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 = &quoted{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  }