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 }