github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/helper/funcs_test.go (about)

     1  package helper
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"sort"
     7  	"testing"
     8  
     9  	"github.com/hashicorp/go-set"
    10  	"github.com/shoenig/test/must"
    11  	"github.com/stretchr/testify/require"
    12  	"golang.org/x/exp/maps"
    13  )
    14  
    15  func Test_Min(t *testing.T) {
    16  	t.Run("int", func(t *testing.T) {
    17  		a := 1
    18  		b := 2
    19  		must.Eq(t, 1, Min(a, b))
    20  		must.Eq(t, 1, Min(b, a))
    21  	})
    22  
    23  	t.Run("float64", func(t *testing.T) {
    24  		a := 1.1
    25  		b := 2.2
    26  		must.Eq(t, 1.1, Min(a, b))
    27  		must.Eq(t, 1.1, Min(b, a))
    28  	})
    29  
    30  	t.Run("string", func(t *testing.T) {
    31  		a := "cat"
    32  		b := "dog"
    33  		must.Eq(t, "cat", Min(a, b))
    34  		must.Eq(t, "cat", Min(b, a))
    35  	})
    36  }
    37  
    38  func Test_Max(t *testing.T) {
    39  	t.Run("int", func(t *testing.T) {
    40  		a := 1
    41  		b := 2
    42  		must.Eq(t, 2, Max(a, b))
    43  		must.Eq(t, 2, Max(b, a))
    44  	})
    45  
    46  	t.Run("float64", func(t *testing.T) {
    47  		a := 1.1
    48  		b := 2.2
    49  		must.Eq(t, 2.2, Max(a, b))
    50  		must.Eq(t, 2.2, Max(b, a))
    51  	})
    52  
    53  	t.Run("string", func(t *testing.T) {
    54  		a := "cat"
    55  		b := "dog"
    56  		must.Eq(t, "dog", Max(a, b))
    57  		must.Eq(t, "dog", Max(b, a))
    58  	})
    59  }
    60  
    61  func TestIsSubset(t *testing.T) {
    62  	l := []string{"a", "b", "c"}
    63  	s := []string{"d"}
    64  
    65  	sub, offending := IsSubset(l, l[:1])
    66  	must.True(t, sub)
    67  	must.SliceEmpty(t, offending)
    68  
    69  	sub, offending = IsSubset(l, s)
    70  	must.False(t, sub)
    71  	must.Eq(t, []string{"d"}, offending)
    72  }
    73  
    74  func TestIsDisjoint(t *testing.T) {
    75  	t.Run("yes", func(t *testing.T) {
    76  		a := []string{"a", "b", "c"}
    77  		b := []string{"d", "f"}
    78  		dis, offending := IsDisjoint(a, b)
    79  		must.True(t, dis)
    80  		must.SliceEmpty(t, offending)
    81  	})
    82  
    83  	t.Run("no", func(t *testing.T) {
    84  		a := []string{"a", "b", "c", "d", "e"}
    85  		b := []string{"b", "c", "f", "g"}
    86  		dis, offending := IsDisjoint(a, b)
    87  		must.False(t, dis)
    88  		must.True(t, set.From(offending).EqualSlice(offending))
    89  	})
    90  }
    91  
    92  func TestStringHasPrefixInSlice(t *testing.T) {
    93  	prefixes := []string{"a", "b", "c", "definitely", "most definitely"}
    94  	// The following strings all start with at least one prefix in the slice above
    95  	require.True(t, StringHasPrefixInSlice("alpha", prefixes))
    96  	require.True(t, StringHasPrefixInSlice("bravo", prefixes))
    97  	require.True(t, StringHasPrefixInSlice("charlie", prefixes))
    98  	require.True(t, StringHasPrefixInSlice("definitely", prefixes))
    99  	require.True(t, StringHasPrefixInSlice("most definitely", prefixes))
   100  
   101  	require.False(t, StringHasPrefixInSlice("mos", prefixes))
   102  	require.False(t, StringHasPrefixInSlice("def", prefixes))
   103  	require.False(t, StringHasPrefixInSlice("delta", prefixes))
   104  
   105  }
   106  
   107  func TestCompareSliceSetString(t *testing.T) {
   108  	cases := []struct {
   109  		A      []string
   110  		B      []string
   111  		Result bool
   112  	}{
   113  		{
   114  			A:      []string{},
   115  			B:      []string{},
   116  			Result: true,
   117  		},
   118  		{
   119  			A:      []string{},
   120  			B:      []string{"a"},
   121  			Result: false,
   122  		},
   123  		{
   124  			A:      []string{"a"},
   125  			B:      []string{"a"},
   126  			Result: true,
   127  		},
   128  		{
   129  			A:      []string{"a"},
   130  			B:      []string{"b"},
   131  			Result: false,
   132  		},
   133  		{
   134  			A:      []string{"a", "b"},
   135  			B:      []string{"b"},
   136  			Result: false,
   137  		},
   138  		{
   139  			A:      []string{"a", "b"},
   140  			B:      []string{"a"},
   141  			Result: false,
   142  		},
   143  		{
   144  			A:      []string{"a", "b"},
   145  			B:      []string{"a", "b"},
   146  			Result: true,
   147  		},
   148  		{
   149  			A:      []string{"a", "b"},
   150  			B:      []string{"b", "a"},
   151  			Result: true,
   152  		},
   153  	}
   154  
   155  	for i, tc := range cases {
   156  		tc := tc
   157  		t.Run(fmt.Sprintf("case-%da", i), func(t *testing.T) {
   158  			if res := SliceSetEq(tc.A, tc.B); res != tc.Result {
   159  				t.Fatalf("expected %t but CompareSliceSetString(%v, %v) -> %t",
   160  					tc.Result, tc.A, tc.B, res,
   161  				)
   162  			}
   163  		})
   164  
   165  		// Function is commutative so compare B and A
   166  		t.Run(fmt.Sprintf("case-%db", i), func(t *testing.T) {
   167  			if res := SliceSetEq(tc.B, tc.A); res != tc.Result {
   168  				t.Fatalf("expected %t but CompareSliceSetString(%v, %v) -> %t",
   169  					tc.Result, tc.B, tc.A, res,
   170  				)
   171  			}
   172  		})
   173  	}
   174  }
   175  
   176  func TestUniqueMapSliceValues(t *testing.T) {
   177  	m := map[string][]string{
   178  		"foo": {"1", "2"},
   179  		"bar": {"3"},
   180  		"baz": nil,
   181  	}
   182  
   183  	act := UniqueMapSliceValues(m)
   184  	exp := []string{"1", "2", "3"}
   185  	sort.Strings(act)
   186  	must.Eq(t, exp, act)
   187  }
   188  
   189  func TestCopyMapStringSliceString(t *testing.T) {
   190  	m := map[string][]string{
   191  		"x": {"a", "b", "c"},
   192  		"y": {"1", "2", "3"},
   193  		"z": nil,
   194  	}
   195  
   196  	c := CopyMapOfSlice(m)
   197  	if !reflect.DeepEqual(c, m) {
   198  		t.Fatalf("%#v != %#v", m, c)
   199  	}
   200  
   201  	c["x"][1] = "---"
   202  	if reflect.DeepEqual(c, m) {
   203  		t.Fatalf("Shared slices: %#v == %#v", m["x"], c["x"])
   204  	}
   205  }
   206  
   207  func TestMergeMapStringString(t *testing.T) {
   208  	type testCase struct {
   209  		map1     map[string]string
   210  		map2     map[string]string
   211  		expected map[string]string
   212  	}
   213  
   214  	cases := []testCase{
   215  		{map[string]string{"foo": "bar"}, map[string]string{"baz": "qux"}, map[string]string{"foo": "bar", "baz": "qux"}},
   216  		{map[string]string{"foo": "bar"}, nil, map[string]string{"foo": "bar"}},
   217  		{nil, map[string]string{"baz": "qux"}, map[string]string{"baz": "qux"}},
   218  		{nil, nil, map[string]string{}},
   219  	}
   220  
   221  	for _, c := range cases {
   222  		if output := MergeMapStringString(c.map1, c.map2); !maps.Equal(output, c.expected) {
   223  			t.Errorf("MergeMapStringString(%q, %q) -> %q != %q", c.map1, c.map2, output, c.expected)
   224  		}
   225  	}
   226  }
   227  
   228  func TestCleanEnvVar(t *testing.T) {
   229  	type testCase struct {
   230  		input    string
   231  		expected string
   232  	}
   233  	cases := []testCase{
   234  		{"asdf", "asdf"},
   235  		{"ASDF", "ASDF"},
   236  		{"0sdf", "_sdf"},
   237  		{"asd0", "asd0"},
   238  		{"_asd", "_asd"},
   239  		{"-asd", "_asd"},
   240  		{"asd.fgh", "asd.fgh"},
   241  		{"A~!@#$%^&*()_+-={}[]|\\;:'\"<,>?/Z", "A______________________________Z"},
   242  		{"A\U0001f4a9Z", "A____Z"},
   243  	}
   244  	for _, c := range cases {
   245  		if output := CleanEnvVar(c.input, '_'); output != c.expected {
   246  			t.Errorf("CleanEnvVar(%q, '_') -> %q != %q", c.input, output, c.expected)
   247  		}
   248  	}
   249  }
   250  
   251  func BenchmarkCleanEnvVar(b *testing.B) {
   252  	in := "NOMAD_ADDR_redis-cache"
   253  	replacement := byte('_')
   254  	b.SetBytes(int64(len(in)))
   255  	b.ReportAllocs()
   256  	b.ResetTimer()
   257  	for i := 0; i < b.N; i++ {
   258  		CleanEnvVar(in, replacement)
   259  	}
   260  }
   261  
   262  type testCase struct {
   263  	input    string
   264  	expected string
   265  }
   266  
   267  func commonCleanFilenameCases() (cases []testCase) {
   268  	// Common set of test cases for all 3 TestCleanFilenameX functions
   269  	cases = []testCase{
   270  		{"asdf", "asdf"},
   271  		{"ASDF", "ASDF"},
   272  		{"0sdf", "0sdf"},
   273  		{"asd0", "asd0"},
   274  		{"_asd", "_asd"},
   275  		{"-asd", "-asd"},
   276  		{"asd.fgh", "asd.fgh"},
   277  		{"Linux/Forbidden", "Linux_Forbidden"},
   278  		{"Windows<>:\"/\\|?*Forbidden", "Windows_________Forbidden"},
   279  		{`Windows<>:"/\|?*Forbidden_StringLiteral`, "Windows_________Forbidden_StringLiteral"},
   280  	}
   281  	return cases
   282  }
   283  
   284  func TestCleanFilename(t *testing.T) {
   285  	cases := append(
   286  		[]testCase{
   287  			{"A\U0001f4a9Z", "A💩Z"}, // CleanFilename allows unicode
   288  			{"A💩Z", "A💩Z"},
   289  			{"A~!@#$%^&*()_+-={}[]|\\;:'\"<,>?/Z", "A~!@#$%^&_()_+-={}[]__;_'__,___Z"},
   290  		}, commonCleanFilenameCases()...)
   291  
   292  	for i, c := range cases {
   293  		t.Run(fmt.Sprintf("case-%d", i), func(t *testing.T) {
   294  			output := CleanFilename(c.input, "_")
   295  			failMsg := fmt.Sprintf("CleanFilename(%q, '_') -> %q != %q", c.input, output, c.expected)
   296  			require.Equal(t, c.expected, output, failMsg)
   297  		})
   298  	}
   299  }
   300  
   301  func TestCleanFilenameASCIIOnly(t *testing.T) {
   302  	ASCIIOnlyCases := append(
   303  		[]testCase{
   304  			{"A\U0001f4a9Z", "A_Z"}, // CleanFilenameASCIIOnly does not allow unicode
   305  			{"A💩Z", "A_Z"},
   306  			{"A~!@#$%^&*()_+-={}[]|\\;:'\"<,>?/Z", "A~!@#$%^&_()_+-={}[]__;_'__,___Z"},
   307  		}, commonCleanFilenameCases()...)
   308  
   309  	for i, c := range ASCIIOnlyCases {
   310  		t.Run(fmt.Sprintf("case-%d", i), func(t *testing.T) {
   311  			output := CleanFilenameASCIIOnly(c.input, "_")
   312  			failMsg := fmt.Sprintf("CleanFilenameASCIIOnly(%q, '_') -> %q != %q", c.input, output, c.expected)
   313  			require.Equal(t, c.expected, output, failMsg)
   314  		})
   315  	}
   316  }
   317  
   318  func TestCleanFilenameStrict(t *testing.T) {
   319  	strictCases := append(
   320  		[]testCase{
   321  			{"A\U0001f4a9Z", "A💩Z"}, // CleanFilenameStrict allows unicode
   322  			{"A💩Z", "A💩Z"},
   323  			{"A~!@#$%^&*()_+-={}[]|\\;:'\"<,>?/Z", "A_!___%^______-_{}_____________Z"},
   324  		}, commonCleanFilenameCases()...)
   325  
   326  	for i, c := range strictCases {
   327  		t.Run(fmt.Sprintf("case-%d", i), func(t *testing.T) {
   328  			output := CleanFilenameStrict(c.input, "_")
   329  			failMsg := fmt.Sprintf("CleanFilenameStrict(%q, '_') -> %q != %q", c.input, output, c.expected)
   330  			require.Equal(t, c.expected, output, failMsg)
   331  		})
   332  	}
   333  }
   334  
   335  func TestCheckNamespaceScope(t *testing.T) {
   336  	cases := []struct {
   337  		desc      string
   338  		provided  string
   339  		requested []string
   340  		offending []string
   341  	}{
   342  		{
   343  			desc:      "root ns requesting namespace",
   344  			provided:  "",
   345  			requested: []string{"engineering"},
   346  		},
   347  		{
   348  			desc:      "matching parent ns with child",
   349  			provided:  "engineering",
   350  			requested: []string{"engineering", "engineering/sub-team"},
   351  		},
   352  		{
   353  			desc:      "mismatch ns",
   354  			provided:  "engineering",
   355  			requested: []string{"finance", "engineering/sub-team", "eng"},
   356  			offending: []string{"finance", "eng"},
   357  		},
   358  		{
   359  			desc:      "mismatch child",
   360  			provided:  "engineering/sub-team",
   361  			requested: []string{"engineering/new-team", "engineering/sub-team", "engineering/sub-team/child"},
   362  			offending: []string{"engineering/new-team"},
   363  		},
   364  		{
   365  			desc:      "matching prefix",
   366  			provided:  "engineering",
   367  			requested: []string{"engineering/new-team", "engineering/new-team/sub-team"},
   368  		},
   369  	}
   370  
   371  	for _, tc := range cases {
   372  		t.Run(tc.desc, func(t *testing.T) {
   373  			offending := CheckNamespaceScope(tc.provided, tc.requested)
   374  			require.Equal(t, offending, tc.offending)
   375  		})
   376  	}
   377  }
   378  
   379  func TestTimer_NewSafeTimer(t *testing.T) {
   380  	t.Run("zero", func(t *testing.T) {
   381  		timer, stop := NewSafeTimer(0)
   382  		defer stop()
   383  		<-timer.C
   384  	})
   385  
   386  	t.Run("positive", func(t *testing.T) {
   387  		timer, stop := NewSafeTimer(1)
   388  		defer stop()
   389  		<-timer.C
   390  	})
   391  }
   392  
   393  func TestTimer_NewStoppedTimer(t *testing.T) {
   394  	timer, stop := NewStoppedTimer()
   395  	defer stop()
   396  
   397  	select {
   398  	case <-timer.C:
   399  		must.Unreachable(t)
   400  	default:
   401  	}
   402  }
   403  
   404  func Test_ConvertSlice(t *testing.T) {
   405  	t.Run("string wrapper", func(t *testing.T) {
   406  
   407  		type wrapper struct{ id string }
   408  		input := []string{"foo", "bar", "bad", "had"}
   409  		cFn := func(id string) *wrapper { return &wrapper{id: id} }
   410  
   411  		expectedOutput := []*wrapper{{id: "foo"}, {id: "bar"}, {id: "bad"}, {id: "had"}}
   412  		actualOutput := ConvertSlice(input, cFn)
   413  		require.ElementsMatch(t, expectedOutput, actualOutput)
   414  	})
   415  
   416  	t.Run("int wrapper", func(t *testing.T) {
   417  
   418  		type wrapper struct{ id int }
   419  		input := []int{10, 13, 1987, 2020}
   420  		cFn := func(id int) *wrapper { return &wrapper{id: id} }
   421  
   422  		expectedOutput := []*wrapper{{id: 10}, {id: 13}, {id: 1987}, {id: 2020}}
   423  		actualOutput := ConvertSlice(input, cFn)
   424  		require.ElementsMatch(t, expectedOutput, actualOutput)
   425  
   426  	})
   427  }
   428  
   429  func Test_IsMethodHTTP(t *testing.T) {
   430  	t.Run("is method", func(t *testing.T) {
   431  		cases := []string{
   432  			"GET", "Get", "get",
   433  			"HEAD", "Head", "head",
   434  			"POST", "Post", "post",
   435  			"PUT", "Put", "put",
   436  			"PATCH", "Patch", "patch",
   437  			"DELETE", "Delete", "delete",
   438  			"CONNECT", "Connect", "connect",
   439  			"OPTIONS", "Options", "options",
   440  			"TRACE", "Trace", "trace",
   441  		}
   442  		for _, tc := range cases {
   443  			result := IsMethodHTTP(tc)
   444  			must.True(t, result)
   445  		}
   446  	})
   447  
   448  	t.Run("is not method", func(t *testing.T) {
   449  		not := []string{"GETTER", "!GET", ""}
   450  		for _, tc := range not {
   451  			result := IsMethodHTTP(tc)
   452  			must.False(t, result)
   453  		}
   454  	})
   455  }
   456  
   457  type employee struct {
   458  	id   int
   459  	name string
   460  }
   461  
   462  func (e *employee) Equal(o *employee) bool {
   463  	return e.id == o.id // name can be different
   464  }
   465  
   466  func Test_ElementsEquals(t *testing.T) {
   467  	t.Run("empty", func(t *testing.T) {
   468  		a := []*employee(nil)
   469  		var b []*employee
   470  		must.True(t, ElementsEqual(a, b))
   471  		must.True(t, ElementsEqual(b, a))
   472  	})
   473  
   474  	t.Run("different sizes", func(t *testing.T) {
   475  		a := []*employee{{1, "mitchell"}, {2, "armon"}, {3, "jack"}}
   476  		b := []*employee{{1, "mitchell"}, {2, "armon"}}
   477  		must.False(t, ElementsEqual(a, b))
   478  		must.False(t, ElementsEqual(b, a))
   479  	})
   480  
   481  	t.Run("equal", func(t *testing.T) {
   482  		a := []*employee{{1, "mitchell"}, {2, "armon"}, {3, "jack"}}
   483  		b := []*employee{{1, "M.H."}, {2, "A.D."}, {3, "J.P."}}
   484  		must.True(t, ElementsEqual(a, b))
   485  		must.True(t, ElementsEqual(b, a))
   486  	})
   487  
   488  	t.Run("different", func(t *testing.T) {
   489  		a := []*employee{{1, "mitchell"}, {2, "armon"}, {3, "jack"}}
   490  		b := []*employee{{0, "mitchell."}, {2, "armon"}, {3, "jack"}}
   491  		must.False(t, ElementsEqual(a, b))
   492  		must.False(t, ElementsEqual(b, a))
   493  	})
   494  }
   495  
   496  func Test_SliceSetEq(t *testing.T) {
   497  	t.Run("empty", func(t *testing.T) {
   498  		a := make([]int, 0)
   499  		b := make([]int, 0)
   500  		must.True(t, SliceSetEq(a, b))
   501  	})
   502  
   503  	t.Run("subset small", func(t *testing.T) {
   504  		a := []int{1, 2, 3, 4, 5}
   505  		b := []int{1, 2, 3}
   506  		must.False(t, SliceSetEq(a, b))
   507  		must.False(t, SliceSetEq(b, a))
   508  	})
   509  
   510  	t.Run("subset large", func(t *testing.T) {
   511  		a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
   512  		b := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
   513  		must.False(t, SliceSetEq(a, b))
   514  		must.False(t, SliceSetEq(b, a))
   515  	})
   516  
   517  	t.Run("same small", func(t *testing.T) {
   518  		a := []int{1, 2, 3, 4, 5}
   519  		b := []int{1, 2, 3, 4, 5}
   520  		must.True(t, SliceSetEq(a, b))
   521  	})
   522  
   523  	t.Run("same large", func(t *testing.T) {
   524  		a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
   525  		b := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
   526  		must.True(t, SliceSetEq(a, b))
   527  	})
   528  }