github.com/unionj-cloud/go-doudou/v2@v2.3.5/toolkit/envconfig/envconfig_test.go (about) 1 // Copyright (c) 2013 Kelsey Hightower. All rights reserved. 2 // Use of this source code is governed by the MIT License that can be found in 3 // the LICENSE file. 4 5 package envconfig 6 7 import ( 8 "flag" 9 "fmt" 10 "github.com/pkg/errors" 11 "net/url" 12 "os" 13 "strings" 14 "testing" 15 "time" 16 ) 17 18 type HonorDecodeInStruct struct { 19 Value string 20 } 21 22 func (h *HonorDecodeInStruct) Decode(env string) error { 23 h.Value = "decoded" 24 return nil 25 } 26 27 type CustomURL struct { 28 Value *url.URL 29 } 30 31 func (cu *CustomURL) UnmarshalBinary(data []byte) error { 32 u, err := url.Parse(string(data)) 33 cu.Value = u 34 return err 35 } 36 37 type Specification struct { 38 Embedded `desc:"can we document a struct"` 39 EmbeddedButIgnored `ignored:"true"` 40 Debug bool 41 Port int 42 Rate float32 43 User string 44 TTL uint32 45 Timeout time.Duration 46 AdminUsers []string 47 Users []string `values_by:"index" required:"true"` 48 Fruits []string `values_by:"index" default:"apple"` 49 MagicNumbers []int 50 EmptyNumbers []int 51 ByteSlice []byte 52 ColorCodes map[string]int 53 MultiWordVar string 54 MultiWordVarWithAutoSplit uint32 `split_words:"true"` 55 MultiWordACRWithAutoSplit uint32 `split_words:"true"` 56 SomePointer *string 57 SomePointerWithDefault *string `default:"foo2baz" desc:"foorbar is the word"` 58 MultiWordVarWithAlt string `envconfig:"MULTI_WORD_VAR_WITH_ALT" desc:"what alt"` 59 MultiWordVarWithLowerCaseAlt string `envconfig:"multi_word_var_with_lower_case_alt"` 60 NoPrefixWithAlt string `envconfig:"SERVICE_HOST"` 61 DefaultVar string `default:"foobar"` 62 RequiredVar string `required:"True"` 63 NoPrefixDefault string `envconfig:"BROKER" default:"127.0.0.1"` 64 RequiredDefault string `required:"true" default:"foo2bar"` 65 Ignored string `ignored:"true"` 66 NestedSpecification struct { 67 Property string `envconfig:"inner"` 68 PropertyWithDefault string `default:"fuzzybydefault"` 69 } `envconfig:"outer"` 70 AfterNested string 71 DecodeStruct HonorDecodeInStruct `envconfig:"honor"` 72 Datetime time.Time 73 MapField map[string]string `default:"one:two,three:four"` 74 UrlValue CustomURL 75 UrlPointer *CustomURL 76 } 77 78 type Embedded struct { 79 Enabled bool `desc:"some embedded value"` 80 EmbeddedPort int 81 MultiWordVar string 82 MultiWordVarWithAlt string `envconfig:"MULTI_WITH_DIFFERENT_ALT"` 83 EmbeddedAlt string `envconfig:"EMBEDDED_WITH_ALT"` 84 EmbeddedIgnored string `ignored:"true"` 85 } 86 87 type EmbeddedButIgnored struct { 88 FirstEmbeddedButIgnored string 89 SecondEmbeddedButIgnored string 90 } 91 92 func TestProcess(t *testing.T) { 93 var s Specification 94 os.Clearenv() 95 os.Setenv("ENV_CONFIG_USERS_0", "John1") 96 os.Setenv("ENV_CONFIG_DEBUG", "true") 97 os.Setenv("ENV_CONFIG_PORT", "8080") 98 os.Setenv("ENV_CONFIG_RATE", "0.5") 99 os.Setenv("ENV_CONFIG_USER", "Kelsey") 100 os.Setenv("ENV_CONFIG_TIMEOUT", "2m") 101 os.Setenv("ENV_CONFIG_ADMINUSERS", "John,Adam,Will") 102 os.Setenv("ENV_CONFIG_MAGICNUMBERS", "5,10,20") 103 os.Setenv("ENV_CONFIG_EMPTYNUMBERS", "") 104 os.Setenv("ENV_CONFIG_BYTESLICE", "this is a test value") 105 os.Setenv("ENV_CONFIG_COLORCODES", "red:1,green:2,blue:3") 106 os.Setenv("SERVICE_HOST", "127.0.0.1") 107 os.Setenv("ENV_CONFIG_TTL", "30") 108 os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") 109 os.Setenv("ENV_CONFIG_IGNORED", "was-not-ignored") 110 os.Setenv("ENV_CONFIG_OUTER_INNER", "iamnested") 111 os.Setenv("ENV_CONFIG_AFTERNESTED", "after") 112 os.Setenv("ENV_CONFIG_HONOR", "honor") 113 os.Setenv("ENV_CONFIG_DATETIME", "2016-08-16T18:57:05Z") 114 os.Setenv("ENV_CONFIG_MULTI_WORD_VAR_WITH_AUTO_SPLIT", "24") 115 os.Setenv("ENV_CONFIG_MULTI_WORD_ACR_WITH_AUTO_SPLIT", "25") 116 os.Setenv("ENV_CONFIG_URLVALUE", "https://github.com/kelseyhightower/envconfig") 117 os.Setenv("ENV_CONFIG_URLPOINTER", "https://github.com/kelseyhightower/envconfig") 118 err := Process("env_config", &s) 119 if err != nil { 120 t.Error(err.Error()) 121 } 122 if s.NoPrefixWithAlt != "127.0.0.1" { 123 t.Errorf("expected %v, got %v", "127.0.0.1", s.NoPrefixWithAlt) 124 } 125 if !s.Debug { 126 t.Errorf("expected %v, got %v", true, s.Debug) 127 } 128 if s.Port != 8080 { 129 t.Errorf("expected %d, got %v", 8080, s.Port) 130 } 131 if s.Rate != 0.5 { 132 t.Errorf("expected %f, got %v", 0.5, s.Rate) 133 } 134 if s.TTL != 30 { 135 t.Errorf("expected %d, got %v", 30, s.TTL) 136 } 137 if s.User != "Kelsey" { 138 t.Errorf("expected %s, got %s", "Kelsey", s.User) 139 } 140 if s.Timeout != 2*time.Minute { 141 t.Errorf("expected %s, got %s", 2*time.Minute, s.Timeout) 142 } 143 if s.RequiredVar != "foo" { 144 t.Errorf("expected %s, got %s", "foo", s.RequiredVar) 145 } 146 if len(s.AdminUsers) != 3 || 147 s.AdminUsers[0] != "John" || 148 s.AdminUsers[1] != "Adam" || 149 s.AdminUsers[2] != "Will" { 150 t.Errorf("expected %#v, got %#v", []string{"John", "Adam", "Will"}, s.AdminUsers) 151 } 152 if len(s.MagicNumbers) != 3 || 153 s.MagicNumbers[0] != 5 || 154 s.MagicNumbers[1] != 10 || 155 s.MagicNumbers[2] != 20 { 156 t.Errorf("expected %#v, got %#v", []int{5, 10, 20}, s.MagicNumbers) 157 } 158 if len(s.EmptyNumbers) != 0 { 159 t.Errorf("expected %#v, got %#v", []int{}, s.EmptyNumbers) 160 } 161 expected := "this is a test value" 162 if string(s.ByteSlice) != expected { 163 t.Errorf("expected %v, got %v", expected, string(s.ByteSlice)) 164 } 165 if s.Ignored != "" { 166 t.Errorf("expected empty string, got %#v", s.Ignored) 167 } 168 169 if len(s.ColorCodes) != 3 || 170 s.ColorCodes["red"] != 1 || 171 s.ColorCodes["green"] != 2 || 172 s.ColorCodes["blue"] != 3 { 173 t.Errorf( 174 "expected %#v, got %#v", 175 map[string]int{ 176 "red": 1, 177 "green": 2, 178 "blue": 3, 179 }, 180 s.ColorCodes, 181 ) 182 } 183 184 if s.NestedSpecification.Property != "iamnested" { 185 t.Errorf("expected '%s' string, got %#v", "iamnested", s.NestedSpecification.Property) 186 } 187 188 if s.NestedSpecification.PropertyWithDefault != "fuzzybydefault" { 189 t.Errorf("expected default '%s' string, got %#v", "fuzzybydefault", s.NestedSpecification.PropertyWithDefault) 190 } 191 192 if s.AfterNested != "after" { 193 t.Errorf("expected default '%s' string, got %#v", "after", s.AfterNested) 194 } 195 196 if s.DecodeStruct.Value != "decoded" { 197 t.Errorf("expected default '%s' string, got %#v", "decoded", s.DecodeStruct.Value) 198 } 199 200 if expected := time.Date(2016, 8, 16, 18, 57, 05, 0, time.UTC); !s.Datetime.Equal(expected) { 201 t.Errorf("expected %s, got %s", expected.Format(time.RFC3339), s.Datetime.Format(time.RFC3339)) 202 } 203 204 if s.MultiWordVarWithAutoSplit != 24 { 205 t.Errorf("expected %q, got %q", 24, s.MultiWordVarWithAutoSplit) 206 } 207 208 if s.MultiWordACRWithAutoSplit != 25 { 209 t.Errorf("expected %d, got %d", 25, s.MultiWordACRWithAutoSplit) 210 } 211 212 u, err := url.Parse("https://github.com/kelseyhightower/envconfig") 213 if err != nil { 214 t.Fatalf("unexpected error: %v", err) 215 } 216 217 if *s.UrlValue.Value != *u { 218 t.Errorf("expected %q, got %q", u, s.UrlValue.Value.String()) 219 } 220 221 if *s.UrlPointer.Value != *u { 222 t.Errorf("expected %q, got %q", u, s.UrlPointer.Value.String()) 223 } 224 } 225 226 func TestProcessValuesBy(t *testing.T) { 227 var s Specification 228 os.Clearenv() 229 os.Setenv("ENV_CONFIG_USERS_0", "John1") 230 os.Setenv("ENV_CONFIG_USERS_1", "Adam2") 231 os.Setenv("ENV_CONFIG_USERS_2", "Will3") 232 os.Setenv("ENV_CONFIG_USERS_3", "Will4") 233 os.Setenv("ENV_CONFIG_USERS_4", "Will5") 234 os.Setenv("ENV_CONFIG_USERS_5", "Will6") 235 os.Setenv("ENV_CONFIG_USERS_6", "Will7") 236 os.Setenv("ENV_CONFIG_USERS_7", "Will8") 237 os.Setenv("ENV_CONFIG_USERS_8", "Will9") 238 os.Setenv("ENV_CONFIG_USERS_9", "Will10") 239 os.Setenv("ENV_CONFIG_USERS_10", "Will11") 240 _ = Process("env_config", &s) 241 if len(s.Users) != 11 { 242 t.Errorf("expected %#v, got %#v", []string{"John1", "Adam2", "Will3", "Will4", "Will5", "Will6", "Will7", "Will8", "Will9", "Will10", "Will11"}, s.Users) 243 } 244 } 245 246 func TestProcessValuesByRequired(t *testing.T) { 247 var s Specification 248 os.Clearenv() 249 err := Process("env_config", &s) 250 if err == nil { 251 t.Errorf("should have error") 252 } 253 if err.Error() != "required key ENV_CONFIG_USERS missing value" { 254 t.Errorf("error not right") 255 } 256 } 257 258 func TestProcessValuesByDefault(t *testing.T) { 259 var s Specification 260 os.Clearenv() 261 os.Setenv("ENV_CONFIG_USERS_0", "John1") 262 os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") 263 err := Process("env_config", &s) 264 if err != nil { 265 t.Errorf("should not have error: %s", err) 266 } 267 if s.Fruits[0] != "apple" { 268 t.Errorf("fruits should have one and only one element apple") 269 } 270 } 271 272 func TestParseErrorBool(t *testing.T) { 273 var s Specification 274 os.Clearenv() 275 os.Setenv("ENV_CONFIG_DEBUG", "string") 276 os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") 277 err := Process("env_config", &s) 278 v := &ParseError{} 279 ok := errors.As(err, &v) 280 if !ok { 281 t.Errorf("expected ParseError, got %v", v) 282 } 283 if v.FieldName != "Debug" { 284 t.Errorf("expected %s, got %v", "Debug", v.FieldName) 285 } 286 if s.Debug != false { 287 t.Errorf("expected %v, got %v", false, s.Debug) 288 } 289 } 290 291 func TestParseErrorFloat32(t *testing.T) { 292 var s Specification 293 os.Clearenv() 294 os.Setenv("ENV_CONFIG_RATE", "string") 295 os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") 296 err := Process("env_config", &s) 297 v := &ParseError{} 298 ok := errors.As(err, &v) 299 if !ok { 300 t.Errorf("expected ParseError, got %v", v) 301 } 302 if v.FieldName != "Rate" { 303 t.Errorf("expected %s, got %v", "Rate", v.FieldName) 304 } 305 if s.Rate != 0 { 306 t.Errorf("expected %v, got %v", 0, s.Rate) 307 } 308 } 309 310 func TestParseErrorInt(t *testing.T) { 311 var s Specification 312 os.Clearenv() 313 os.Setenv("ENV_CONFIG_PORT", "string") 314 os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") 315 err := Process("env_config", &s) 316 v := &ParseError{} 317 ok := errors.As(err, &v) 318 if !ok { 319 t.Errorf("expected ParseError, got %v", v) 320 } 321 if v.FieldName != "Port" { 322 t.Errorf("expected %s, got %v", "Port", v.FieldName) 323 } 324 if s.Port != 0 { 325 t.Errorf("expected %v, got %v", 0, s.Port) 326 } 327 } 328 329 func TestParseErrorUint(t *testing.T) { 330 var s Specification 331 os.Clearenv() 332 os.Setenv("ENV_CONFIG_TTL", "-30") 333 err := Process("env_config", &s) 334 v := &ParseError{} 335 ok := errors.As(err, &v) 336 if !ok { 337 t.Errorf("expected ParseError, got %v", v) 338 } 339 if v.FieldName != "TTL" { 340 t.Errorf("expected %s, got %v", "TTL", v.FieldName) 341 } 342 if s.TTL != 0 { 343 t.Errorf("expected %v, got %v", 0, s.TTL) 344 } 345 } 346 347 func TestParseErrorSplitWords(t *testing.T) { 348 var s Specification 349 os.Clearenv() 350 os.Setenv("ENV_CONFIG_MULTI_WORD_VAR_WITH_AUTO_SPLIT", "shakespeare") 351 os.Setenv("ENV_CONFIG_USERS_0", "John1") 352 err := Process("env_config", &s) 353 v := &ParseError{} 354 ok := errors.As(err, &v) 355 if !ok { 356 t.Errorf("expected ParseError, got %v", v) 357 } 358 if v.FieldName != "MultiWordVarWithAutoSplit" { 359 t.Errorf("expected %s, got %v", "", v.FieldName) 360 } 361 if s.MultiWordVarWithAutoSplit != 0 { 362 t.Errorf("expected %v, got %v", 0, s.MultiWordVarWithAutoSplit) 363 } 364 } 365 366 func TestErrInvalidSpecification(t *testing.T) { 367 m := make(map[string]string) 368 err := Process("env_config", &m) 369 if err != ErrInvalidSpecification { 370 t.Errorf("expected %v, got %v", ErrInvalidSpecification, err) 371 } 372 } 373 374 func TestUnsetVars(t *testing.T) { 375 var s Specification 376 os.Clearenv() 377 os.Setenv("USER", "foo") 378 os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") 379 os.Setenv("ENV_CONFIG_USERS_0", "John1") 380 if err := Process("env_config", &s); err != nil { 381 t.Error(err.Error()) 382 } 383 384 // If the var is not defined the non-prefixed version should not be used 385 // unless the struct tag says so 386 if s.User != "" { 387 t.Errorf("expected %q, got %q", "", s.User) 388 } 389 } 390 391 func TestAlternateVarNames(t *testing.T) { 392 var s Specification 393 os.Clearenv() 394 os.Setenv("ENV_CONFIG_MULTI_WORD_VAR", "foo") 395 os.Setenv("ENV_CONFIG_MULTI_WORD_VAR_WITH_ALT", "bar") 396 os.Setenv("ENV_CONFIG_MULTI_WORD_VAR_WITH_LOWER_CASE_ALT", "baz") 397 os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") 398 os.Setenv("ENV_CONFIG_USERS_0", "John1") 399 if err := Process("env_config", &s); err != nil { 400 t.Error(err.Error()) 401 } 402 403 // Setting the alt version of the var in the environment has no effect if 404 // the struct tag is not supplied 405 if s.MultiWordVar != "" { 406 t.Errorf("expected %q, got %q", "", s.MultiWordVar) 407 } 408 409 // Setting the alt version of the var in the environment correctly sets 410 // the value if the struct tag IS supplied 411 if s.MultiWordVarWithAlt != "bar" { 412 t.Errorf("expected %q, got %q", "bar", s.MultiWordVarWithAlt) 413 } 414 415 // Alt value is not case sensitive and is treated as all uppercase 416 if s.MultiWordVarWithLowerCaseAlt != "baz" { 417 t.Errorf("expected %q, got %q", "baz", s.MultiWordVarWithLowerCaseAlt) 418 } 419 } 420 421 func TestRequiredVar(t *testing.T) { 422 var s Specification 423 os.Clearenv() 424 os.Setenv("ENV_CONFIG_REQUIREDVAR", "foobar") 425 os.Setenv("ENV_CONFIG_USERS_0", "John1") 426 if err := Process("env_config", &s); err != nil { 427 t.Error(err.Error()) 428 } 429 430 if s.RequiredVar != "foobar" { 431 t.Errorf("expected %s, got %s", "foobar", s.RequiredVar) 432 } 433 } 434 435 func TestRequiredMissing(t *testing.T) { 436 var s Specification 437 os.Clearenv() 438 439 err := Process("env_config", &s) 440 if err == nil { 441 t.Error("no failure when missing required variable") 442 } 443 } 444 445 func TestBlankDefaultVar(t *testing.T) { 446 var s Specification 447 os.Clearenv() 448 os.Setenv("ENV_CONFIG_REQUIREDVAR", "requiredvalue") 449 os.Setenv("ENV_CONFIG_USERS_0", "John1") 450 if err := Process("env_config", &s); err != nil { 451 t.Error(err.Error()) 452 } 453 454 if s.DefaultVar != "foobar" { 455 t.Errorf("expected %s, got %s", "foobar", s.DefaultVar) 456 } 457 458 if *s.SomePointerWithDefault != "foo2baz" { 459 t.Errorf("expected %s, got %s", "foo2baz", *s.SomePointerWithDefault) 460 } 461 } 462 463 func TestNonBlankDefaultVar(t *testing.T) { 464 var s Specification 465 os.Clearenv() 466 os.Setenv("ENV_CONFIG_DEFAULTVAR", "nondefaultval") 467 os.Setenv("ENV_CONFIG_REQUIREDVAR", "requiredvalue") 468 os.Setenv("ENV_CONFIG_USERS_0", "John1") 469 if err := Process("env_config", &s); err != nil { 470 t.Error(err.Error()) 471 } 472 473 if s.DefaultVar != "nondefaultval" { 474 t.Errorf("expected %s, got %s", "nondefaultval", s.DefaultVar) 475 } 476 } 477 478 func TestExplicitBlankDefaultVar(t *testing.T) { 479 var s Specification 480 os.Clearenv() 481 os.Setenv("ENV_CONFIG_DEFAULTVAR", "") 482 os.Setenv("ENV_CONFIG_REQUIREDVAR", "") 483 os.Setenv("ENV_CONFIG_USERS_0", "John1") 484 if err := Process("env_config", &s); err != nil { 485 t.Error(err.Error()) 486 } 487 488 if s.DefaultVar != "" { 489 t.Errorf("expected %s, got %s", "\"\"", s.DefaultVar) 490 } 491 } 492 493 func TestAlternateNameDefaultVar(t *testing.T) { 494 var s Specification 495 os.Clearenv() 496 os.Setenv("BROKER", "betterbroker") 497 os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") 498 os.Setenv("ENV_CONFIG_USERS_0", "John1") 499 if err := Process("env_config", &s); err != nil { 500 t.Error(err.Error()) 501 } 502 503 if s.NoPrefixDefault != "betterbroker" { 504 t.Errorf("expected %q, got %q", "betterbroker", s.NoPrefixDefault) 505 } 506 507 os.Clearenv() 508 os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") 509 if err := Process("env_config", &s); err != nil { 510 t.Error(err.Error()) 511 } 512 513 if s.NoPrefixDefault != "127.0.0.1" { 514 t.Errorf("expected %q, got %q", "127.0.0.1", s.NoPrefixDefault) 515 } 516 } 517 518 func TestRequiredDefault(t *testing.T) { 519 var s Specification 520 os.Clearenv() 521 os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") 522 os.Setenv("ENV_CONFIG_USERS_0", "John1") 523 if err := Process("env_config", &s); err != nil { 524 t.Error(err.Error()) 525 } 526 527 if s.RequiredDefault != "foo2bar" { 528 t.Errorf("expected %q, got %q", "foo2bar", s.RequiredDefault) 529 } 530 } 531 532 func TestPointerFieldBlank(t *testing.T) { 533 var s Specification 534 os.Clearenv() 535 os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") 536 os.Setenv("ENV_CONFIG_USERS_0", "John1") 537 if err := Process("env_config", &s); err != nil { 538 t.Error(err.Error()) 539 } 540 541 if s.SomePointer != nil { 542 t.Errorf("expected <nil>, got %q", *s.SomePointer) 543 } 544 } 545 546 func TestEmptyMapFieldOverride(t *testing.T) { 547 var s Specification 548 os.Clearenv() 549 os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") 550 os.Setenv("ENV_CONFIG_MAPFIELD", "") 551 os.Setenv("ENV_CONFIG_USERS_0", "John1") 552 if err := Process("env_config", &s); err != nil { 553 t.Error(err.Error()) 554 } 555 556 if s.MapField == nil { 557 t.Error("expected empty map, got <nil>") 558 } 559 560 if len(s.MapField) != 0 { 561 t.Errorf("expected empty map, got map of size %d", len(s.MapField)) 562 } 563 } 564 565 func TestMustProcess(t *testing.T) { 566 var s Specification 567 os.Clearenv() 568 os.Setenv("ENV_CONFIG_DEBUG", "true") 569 os.Setenv("ENV_CONFIG_PORT", "8080") 570 os.Setenv("ENV_CONFIG_RATE", "0.5") 571 os.Setenv("ENV_CONFIG_USER", "Kelsey") 572 os.Setenv("SERVICE_HOST", "127.0.0.1") 573 os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") 574 os.Setenv("ENV_CONFIG_USERS_0", "John1") 575 MustProcess("env_config", &s) 576 577 defer func() { 578 if err := recover(); err != nil { 579 return 580 } 581 582 t.Error("expected panic") 583 }() 584 m := make(map[string]string) 585 MustProcess("env_config", &m) 586 } 587 588 func TestEmbeddedStruct(t *testing.T) { 589 var s Specification 590 os.Clearenv() 591 os.Setenv("ENV_CONFIG_REQUIREDVAR", "required") 592 os.Setenv("ENV_CONFIG_ENABLED", "true") 593 os.Setenv("ENV_CONFIG_EMBEDDEDPORT", "1234") 594 os.Setenv("ENV_CONFIG_MULTIWORDVAR", "foo") 595 os.Setenv("ENV_CONFIG_MULTI_WORD_VAR_WITH_ALT", "bar") 596 os.Setenv("ENV_CONFIG_MULTI_WITH_DIFFERENT_ALT", "baz") 597 os.Setenv("ENV_CONFIG_EMBEDDED_WITH_ALT", "foobar") 598 os.Setenv("ENV_CONFIG_SOMEPOINTER", "foobaz") 599 os.Setenv("ENV_CONFIG_EMBEDDED_IGNORED", "was-not-ignored") 600 os.Setenv("ENV_CONFIG_USERS_0", "John1") 601 if err := Process("env_config", &s); err != nil { 602 t.Error(err.Error()) 603 } 604 if !s.Enabled { 605 t.Errorf("expected %v, got %v", true, s.Enabled) 606 } 607 if s.EmbeddedPort != 1234 { 608 t.Errorf("expected %d, got %v", 1234, s.EmbeddedPort) 609 } 610 if s.MultiWordVar != "foo" { 611 t.Errorf("expected %s, got %s", "foo", s.MultiWordVar) 612 } 613 if s.Embedded.MultiWordVar != "foo" { 614 t.Errorf("expected %s, got %s", "foo", s.Embedded.MultiWordVar) 615 } 616 if s.MultiWordVarWithAlt != "bar" { 617 t.Errorf("expected %s, got %s", "bar", s.MultiWordVarWithAlt) 618 } 619 if s.Embedded.MultiWordVarWithAlt != "baz" { 620 t.Errorf("expected %s, got %s", "baz", s.Embedded.MultiWordVarWithAlt) 621 } 622 if s.EmbeddedAlt != "foobar" { 623 t.Errorf("expected %s, got %s", "foobar", s.EmbeddedAlt) 624 } 625 if *s.SomePointer != "foobaz" { 626 t.Errorf("expected %s, got %s", "foobaz", *s.SomePointer) 627 } 628 if s.EmbeddedIgnored != "" { 629 t.Errorf("expected empty string, got %#v", s.Ignored) 630 } 631 } 632 633 func TestEmbeddedButIgnoredStruct(t *testing.T) { 634 var s Specification 635 os.Clearenv() 636 os.Setenv("ENV_CONFIG_REQUIREDVAR", "required") 637 os.Setenv("ENV_CONFIG_FIRSTEMBEDDEDBUTIGNORED", "was-not-ignored") 638 os.Setenv("ENV_CONFIG_SECONDEMBEDDEDBUTIGNORED", "was-not-ignored") 639 os.Setenv("ENV_CONFIG_USERS_0", "John1") 640 if err := Process("env_config", &s); err != nil { 641 t.Error(err.Error()) 642 } 643 if s.FirstEmbeddedButIgnored != "" { 644 t.Errorf("expected empty string, got %#v", s.Ignored) 645 } 646 if s.SecondEmbeddedButIgnored != "" { 647 t.Errorf("expected empty string, got %#v", s.Ignored) 648 } 649 } 650 651 func TestNonPointerFailsProperly(t *testing.T) { 652 var s Specification 653 os.Clearenv() 654 os.Setenv("ENV_CONFIG_REQUIREDVAR", "snap") 655 656 err := Process("env_config", s) 657 if err != ErrInvalidSpecification { 658 t.Errorf("non-pointer should fail with ErrInvalidSpecification, was instead %s", err) 659 } 660 } 661 662 func TestCustomValueFields(t *testing.T) { 663 var s struct { 664 Foo string 665 Bar bracketed 666 Baz quoted 667 Struct setterStruct 668 } 669 670 // Set would panic when the receiver is nil, 671 // so make sure it has an initial value to replace. 672 s.Baz = quoted{new(bracketed)} 673 674 os.Clearenv() 675 os.Setenv("ENV_CONFIG_FOO", "foo") 676 os.Setenv("ENV_CONFIG_BAR", "bar") 677 os.Setenv("ENV_CONFIG_BAZ", "baz") 678 os.Setenv("ENV_CONFIG_STRUCT", "inner") 679 680 if err := Process("env_config", &s); err != nil { 681 t.Error(err.Error()) 682 } 683 684 if want := "foo"; s.Foo != want { 685 t.Errorf("foo: got %#q, want %#q", s.Foo, want) 686 } 687 688 if want := "[bar]"; s.Bar.String() != want { 689 t.Errorf("bar: got %#q, want %#q", s.Bar, want) 690 } 691 692 if want := `["baz"]`; s.Baz.String() != want { 693 t.Errorf(`baz: got %#q, want %#q`, s.Baz, want) 694 } 695 696 if want := `setterstruct{"inner"}`; s.Struct.Inner != want { 697 t.Errorf(`Struct.Inner: got %#q, want %#q`, s.Struct.Inner, want) 698 } 699 } 700 701 func TestCustomPointerFields(t *testing.T) { 702 var s struct { 703 Foo string 704 Bar *bracketed 705 Baz *quoted 706 Struct *setterStruct 707 } 708 709 // Set would panic when the receiver is nil, 710 // so make sure they have initial values to replace. 711 s.Bar = new(bracketed) 712 s.Baz = "ed{new(bracketed)} 713 714 os.Clearenv() 715 os.Setenv("ENV_CONFIG_FOO", "foo") 716 os.Setenv("ENV_CONFIG_BAR", "bar") 717 os.Setenv("ENV_CONFIG_BAZ", "baz") 718 os.Setenv("ENV_CONFIG_STRUCT", "inner") 719 720 if err := Process("env_config", &s); err != nil { 721 t.Error(err.Error()) 722 } 723 724 if want := "foo"; s.Foo != want { 725 t.Errorf("foo: got %#q, want %#q", s.Foo, want) 726 } 727 728 if want := "[bar]"; s.Bar.String() != want { 729 t.Errorf("bar: got %#q, want %#q", s.Bar, want) 730 } 731 732 if want := `["baz"]`; s.Baz.String() != want { 733 t.Errorf(`baz: got %#q, want %#q`, s.Baz, want) 734 } 735 736 if want := `setterstruct{"inner"}`; s.Struct.Inner != want { 737 t.Errorf(`Struct.Inner: got %#q, want %#q`, s.Struct.Inner, want) 738 } 739 } 740 741 func TestEmptyPrefixUsesFieldNames(t *testing.T) { 742 var s Specification 743 os.Clearenv() 744 os.Setenv("REQUIREDVAR", "foo") 745 os.Setenv("USERS_0", "John1") 746 err := Process("", &s) 747 if err != nil { 748 t.Errorf("Process failed: %s", err) 749 } 750 751 if s.RequiredVar != "foo" { 752 t.Errorf( 753 `RequiredVar not populated correctly: expected "foo", got %q`, 754 s.RequiredVar, 755 ) 756 } 757 } 758 759 func TestNestedStructVarName(t *testing.T) { 760 var s Specification 761 os.Clearenv() 762 os.Setenv("ENV_CONFIG_REQUIREDVAR", "required") 763 os.Setenv("ENV_CONFIG_USERS_0", "John1") 764 val := "found with only short name" 765 os.Setenv("INNER", val) 766 if err := Process("env_config", &s); err != nil { 767 t.Error(err.Error()) 768 } 769 if s.NestedSpecification.Property != val { 770 t.Errorf("expected %s, got %s", val, s.NestedSpecification.Property) 771 } 772 } 773 774 func TestTextUnmarshalerError(t *testing.T) { 775 var s Specification 776 os.Clearenv() 777 os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") 778 os.Setenv("ENV_CONFIG_DATETIME", "I'M NOT A DATE") 779 os.Setenv("ENV_CONFIG_USERS_0", "John1") 780 err := Process("env_config", &s) 781 782 v := &ParseError{} 783 ok := errors.As(err, &v) 784 if !ok { 785 t.Errorf("expected ParseError, got %v", v) 786 } 787 if v.FieldName != "Datetime" { 788 t.Errorf("expected %s, got %v", "Datetime", v.FieldName) 789 } 790 791 expectedLowLevelError := time.ParseError{ 792 Layout: time.RFC3339, 793 Value: "I'M NOT A DATE", 794 LayoutElem: "2006", 795 ValueElem: "I'M NOT A DATE", 796 } 797 798 if v.Err.Error() != expectedLowLevelError.Error() { 799 t.Errorf("expected %s, got %s", expectedLowLevelError, v.Err) 800 } 801 } 802 803 func TestBinaryUnmarshalerError(t *testing.T) { 804 var s Specification 805 os.Clearenv() 806 os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") 807 os.Setenv("ENV_CONFIG_URLPOINTER", "http://%41:8080/") 808 os.Setenv("ENV_CONFIG_USERS_0", "John1") 809 err := Process("env_config", &s) 810 811 v := &ParseError{} 812 ok := errors.As(err, &v) 813 if !ok { 814 t.Fatalf("expected ParseError, got %T %v", err, err) 815 } 816 if v.FieldName != "UrlPointer" { 817 t.Errorf("expected %s, got %v", "UrlPointer", v.FieldName) 818 } 819 820 // To be compatible with go 1.5 and lower we should do a very basic check, 821 // because underlying error message varies in go 1.5 and go 1.6+. 822 ue := &url.Error{} 823 ok = errors.As(v.Err, &ue) 824 if !ok { 825 t.Errorf("expected error type to be \"*url.Error\", got %T", v.Err) 826 } 827 828 if ue.Op != "parse" { 829 t.Errorf("expected error op to be \"parse\", got %q", ue.Op) 830 } 831 } 832 833 func TestCheckDisallowedOnlyAllowed(t *testing.T) { 834 var s Specification 835 os.Clearenv() 836 os.Setenv("ENV_CONFIG_DEBUG", "true") 837 os.Setenv("UNRELATED_ENV_VAR", "true") 838 err := CheckDisallowed("env_config", &s) 839 if err != nil { 840 t.Errorf("expected no error, got %s", err) 841 } 842 } 843 844 func TestCheckDisallowedMispelled(t *testing.T) { 845 var s Specification 846 os.Clearenv() 847 os.Setenv("ENV_CONFIG_DEBUG", "true") 848 os.Setenv("ENV_CONFIG_ZEBUG", "false") 849 err := CheckDisallowed("env_config", &s) 850 if experr := "unknown environment variable ENV_CONFIG_ZEBUG"; err.Error() != experr { 851 t.Errorf("expected %s, got %s", experr, err) 852 } 853 } 854 855 func TestCheckDisallowedIgnored(t *testing.T) { 856 var s Specification 857 os.Clearenv() 858 os.Setenv("ENV_CONFIG_DEBUG", "true") 859 os.Setenv("ENV_CONFIG_IGNORED", "false") 860 err := CheckDisallowed("env_config", &s) 861 if experr := "unknown environment variable ENV_CONFIG_IGNORED"; err.Error() != experr { 862 t.Errorf("expected %s, got %s", experr, err) 863 } 864 } 865 866 func TestErrorMessageForRequiredAltVar(t *testing.T) { 867 var s struct { 868 Foo string `envconfig:"BAR" required:"true"` 869 } 870 871 os.Clearenv() 872 err := Process("env_config", &s) 873 874 if err == nil { 875 t.Error("no failure when missing required variable") 876 } 877 878 if !strings.Contains(err.Error(), " BAR ") { 879 t.Errorf("expected error message to contain BAR, got \"%v\"", err) 880 } 881 } 882 883 type bracketed string 884 885 func (b *bracketed) Set(value string) error { 886 *b = bracketed("[" + value + "]") 887 return nil 888 } 889 890 func (b bracketed) String() string { 891 return string(b) 892 } 893 894 // quoted is used to test the precedence of Decode over Set. 895 // The sole field is a flag.Value rather than a setter to validate that 896 // all flag.Value implementations are also Setter implementations. 897 type quoted struct{ flag.Value } 898 899 func (d quoted) Decode(value string) error { 900 return d.Set(`"` + value + `"`) 901 } 902 903 type setterStruct struct { 904 Inner string 905 } 906 907 func (ss *setterStruct) Set(value string) error { 908 ss.Inner = fmt.Sprintf("setterstruct{%q}", value) 909 return nil 910 } 911 912 func BenchmarkGatherInfo(b *testing.B) { 913 os.Clearenv() 914 os.Setenv("ENV_CONFIG_DEBUG", "true") 915 os.Setenv("ENV_CONFIG_PORT", "8080") 916 os.Setenv("ENV_CONFIG_RATE", "0.5") 917 os.Setenv("ENV_CONFIG_USER", "Kelsey") 918 os.Setenv("ENV_CONFIG_TIMEOUT", "2m") 919 os.Setenv("ENV_CONFIG_ADMINUSERS", "John,Adam,Will") 920 os.Setenv("ENV_CONFIG_MAGICNUMBERS", "5,10,20") 921 os.Setenv("ENV_CONFIG_COLORCODES", "red:1,green:2,blue:3") 922 os.Setenv("SERVICE_HOST", "127.0.0.1") 923 os.Setenv("ENV_CONFIG_TTL", "30") 924 os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") 925 os.Setenv("ENV_CONFIG_IGNORED", "was-not-ignored") 926 os.Setenv("ENV_CONFIG_OUTER_INNER", "iamnested") 927 os.Setenv("ENV_CONFIG_AFTERNESTED", "after") 928 os.Setenv("ENV_CONFIG_HONOR", "honor") 929 os.Setenv("ENV_CONFIG_DATETIME", "2016-08-16T18:57:05Z") 930 os.Setenv("ENV_CONFIG_MULTI_WORD_VAR_WITH_AUTO_SPLIT", "24") 931 for i := 0; i < b.N; i++ { 932 var s Specification 933 gatherInfo("env_config", &s) 934 } 935 }