github.com/ardanlabs/conf/v2@v2.2.0/conf_test.go (about)

     1  package conf_test
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/ardanlabs/conf/v2"
    11  	"github.com/ardanlabs/conf/v2/yaml"
    12  	"github.com/google/go-cmp/cmp"
    13  )
    14  
    15  const (
    16  	success = "\u2713"
    17  	failed  = "\u2717"
    18  )
    19  
    20  // =============================================================================
    21  
    22  // CustomValue provides support for testing a custom value.
    23  type CustomValue struct {
    24  	something string
    25  }
    26  
    27  // Set implements the Setter interface
    28  func (c *CustomValue) Set(data string) error {
    29  	*c = CustomValue{something: fmt.Sprintf("@%s@", data)}
    30  	return nil
    31  }
    32  
    33  // String implements the Stringer interface
    34  func (c CustomValue) String() string {
    35  	return c.something
    36  }
    37  
    38  // Equal implements the Equal "interface" for go-cmp
    39  func (c CustomValue) Equal(o CustomValue) bool {
    40  	return c.something == o.something
    41  }
    42  
    43  // =============================================================================
    44  
    45  type ip struct {
    46  	Name      string   `conf:"default:localhost,env:IP_NAME_VAR"`
    47  	IP        string   `conf:"default:127.0.0.0"`
    48  	Endpoints []string `conf:"default:127.0.0.1:200;127.0.0.1:829"`
    49  }
    50  type Embed struct {
    51  	Name     string        `conf:"default:bill"`
    52  	Duration time.Duration `conf:"default:1s,flag:e-dur,short:d"`
    53  }
    54  type config struct {
    55  	AnInt     int    `conf:"default:9"`
    56  	AString   string `conf:"default:B,short:s"`
    57  	Bool      bool
    58  	Skip      string `conf:"-"`
    59  	IP        ip
    60  	DebugHost string      `conf:"default:http://user:password@0.0.0.0:4000,mask"`
    61  	Password  string      `conf:"default:password,mask"`
    62  	Custom    CustomValue `conf:"default:hello"`
    63  	Embed
    64  }
    65  
    66  // =============================================================================
    67  func TestRequired(t *testing.T) {
    68  	t.Logf("\tTest: %d\tWhen required values are missing.", 1)
    69  	{
    70  		f := func(t *testing.T) {
    71  			os.Args = []string{"conf.test"}
    72  			var cfg struct {
    73  				TestInt    int `conf:"required, default:1"`
    74  				TestString string
    75  				TestBool   bool
    76  			}
    77  			_, err := conf.Parse("TEST", &cfg)
    78  			if err == nil {
    79  				t.Fatalf("\t%s\tShould fail for missing required value.", failed)
    80  			}
    81  			t.Logf("\t%s\tShould fail for missing required value : %s", success, err)
    82  		}
    83  		t.Run("required-missing-value", f)
    84  	}
    85  
    86  	t.Logf("\tTest: %d\tWhen struct has no fields.", 2)
    87  	{
    88  		f := func(t *testing.T) {
    89  			os.Args = []string{"conf.test"}
    90  			var cfg struct {
    91  				testInt    int `conf:"required, default:1"`
    92  				testString string
    93  				testBool   bool
    94  			}
    95  			_, err := conf.Parse("TEST", &cfg)
    96  			if err == nil {
    97  				t.Fatalf("\t%s\tShould fail for struct with no exported fields.", failed)
    98  			}
    99  			t.Logf("\t%s\tShould fail for struct with no exported fields : %s", success, err)
   100  		}
   101  		t.Run("struct-missing-fields", f)
   102  	}
   103  
   104  	t.Logf("\tTest: %d\tWhen required values exist and are passed on args.", 3)
   105  	{
   106  		f := func(t *testing.T) {
   107  			os.Args = []string{"conf.test", "--test-int", "1"}
   108  
   109  			var cfg struct {
   110  				TestInt    int `conf:"required, default:1"`
   111  				TestString string
   112  				TestBool   bool
   113  			}
   114  			_, err := conf.Parse("TEST", &cfg)
   115  			if err != nil {
   116  				t.Fatalf("\t%s\tShould have parsed the required field on args : %s", failed, err)
   117  			}
   118  			t.Logf("\t%s\tShould have parsed the required field on args.", success)
   119  		}
   120  		t.Run("required-existing-fields-args", f)
   121  	}
   122  
   123  	t.Logf("\tTest: %d\tWhen required values exist and are passed on env.", 4)
   124  	{
   125  		f := func(t *testing.T) {
   126  			os.Args = []string{"conf.test"}
   127  			os.Setenv("TEST_TEST_INT", "1")
   128  
   129  			var cfg struct {
   130  				TestInt    int `conf:"required, default:1"`
   131  				TestString string
   132  				TestBool   bool
   133  			}
   134  			_, err := conf.Parse("TEST", &cfg)
   135  			if err != nil {
   136  				t.Fatalf("\t%s\tShould have parsed the required field on Env : %s", failed, err)
   137  			}
   138  			t.Logf("\t%s\tShould have parsed the required field on Env.", success)
   139  		}
   140  		t.Run("required-existing-fields-args", f)
   141  	}
   142  }
   143  
   144  func TestParse(t *testing.T) {
   145  	tests := []struct {
   146  		name string
   147  		envs map[string]string
   148  		args []string
   149  		want config
   150  	}{
   151  		{
   152  			"default",
   153  			nil,
   154  			nil,
   155  			config{9, "B", false, "", ip{"localhost", "127.0.0.0", []string{"127.0.0.1:200", "127.0.0.1:829"}}, "http://user:password@0.0.0.0:4000", "password", CustomValue{something: "@hello@"}, Embed{"bill", time.Second}},
   156  		},
   157  		{
   158  			"env",
   159  			map[string]string{"TEST_AN_INT": "1", "TEST_A_STRING": "s", "TEST_BOOL": "TRUE", "TEST_SKIP": "SKIP", "TEST_IP_NAME_VAR": "local", "TEST_DEBUG_HOST": "http://bill:gopher@0.0.0.0:4000", "TEST_PASSWORD": "gopher", "TEST_NAME": "andy", "TEST_DURATION": "1m"},
   160  			nil,
   161  			config{1, "s", true, "", ip{"local", "127.0.0.0", []string{"127.0.0.1:200", "127.0.0.1:829"}}, "http://bill:gopher@0.0.0.0:4000", "gopher", CustomValue{something: "@hello@"}, Embed{"andy", time.Minute}},
   162  		},
   163  		{
   164  			"flag",
   165  			nil,
   166  			[]string{"conf.test", "--an-int", "1", "-s", "s", "--bool", "--skip", "skip", "--ip-name", "local", "--debug-host", "http://bill:gopher@0.0.0.0:4000", "--password", "gopher", "--name", "andy", "--e-dur", "1m"},
   167  			config{1, "s", true, "", ip{"local", "127.0.0.0", []string{"127.0.0.1:200", "127.0.0.1:829"}}, "http://bill:gopher@0.0.0.0:4000", "gopher", CustomValue{something: "@hello@"}, Embed{"andy", time.Minute}},
   168  		},
   169  		{
   170  			"multi",
   171  			map[string]string{"TEST_A_STRING": "s", "TEST_BOOL": "TRUE", "TEST_IP_NAME_VAR": "local", "TEST_DEBUG_HOST": "http://bill:gopher@0.0.0.0:4000", "TEST_PASSWORD": "gopher", "TEST_NAME": "andy", "TEST_DURATION": "1m"},
   172  			[]string{"conf.test", "--an-int", "2", "--bool", "--skip", "skip", "--name", "jack", "-d", "1ms"},
   173  			config{2, "s", true, "", ip{"local", "127.0.0.0", []string{"127.0.0.1:200", "127.0.0.1:829"}}, "http://bill:gopher@0.0.0.0:4000", "gopher", CustomValue{something: "@hello@"}, Embed{"jack", time.Millisecond}},
   174  		},
   175  	}
   176  
   177  	t.Log("Given the need to parse basic configuration.")
   178  	{
   179  		for i, tt := range tests {
   180  			t.Logf("\tTest: %d\tWhen checking with arguments %v", i, tt.args)
   181  			{
   182  				os.Clearenv()
   183  				for k, v := range tt.envs {
   184  					os.Setenv(k, v)
   185  				}
   186  
   187  				f := func(t *testing.T) {
   188  					os.Args = tt.args
   189  
   190  					var cfg config
   191  					if _, err := conf.Parse("TEST", &cfg); err != nil {
   192  						t.Fatalf("\t%s\tShould be able to Parse arguments : %s.", failed, err)
   193  					}
   194  					t.Logf("\t%s\tShould be able to Parse arguments.", success)
   195  
   196  					if diff := cmp.Diff(tt.want, cfg); diff != "" {
   197  						t.Fatalf("\t%s\tShould have properly initialized struct value\n%s", failed, diff)
   198  					}
   199  					t.Logf("\t%s\tShould have properly initialized struct value.", success)
   200  				}
   201  
   202  				t.Run(tt.name, f)
   203  			}
   204  		}
   205  	}
   206  }
   207  
   208  func TestParseEmptyNamespace(t *testing.T) {
   209  	tests := []struct {
   210  		name string
   211  		envs map[string]string
   212  		args []string
   213  		want config
   214  	}{
   215  		{
   216  			"env",
   217  			map[string]string{"AN_INT": "1", "A_STRING": "s", "BOOL": "TRUE", "SKIP": "SKIP", "IP_NAME_VAR": "local", "DEBUG_HOST": "http://bill:gopher@0.0.0.0:4000", "PASSWORD": "gopher", "NAME": "andy", "DURATION": "1m"},
   218  			nil,
   219  			config{1, "s", true, "", ip{"local", "127.0.0.0", []string{"127.0.0.1:200", "127.0.0.1:829"}}, "http://bill:gopher@0.0.0.0:4000", "gopher", CustomValue{something: "@hello@"}, Embed{"andy", time.Minute}},
   220  		},
   221  	}
   222  
   223  	t.Log("Given the need to parse basic configuration.")
   224  	{
   225  		for i, tt := range tests {
   226  			t.Logf("\tTest: %d\tWhen checking with arguments %v", i, tt.args)
   227  			{
   228  				os.Clearenv()
   229  				for k, v := range tt.envs {
   230  					os.Setenv(k, v)
   231  				}
   232  
   233  				f := func(t *testing.T) {
   234  					os.Args = tt.args
   235  
   236  					var cfg config
   237  					if _, err := conf.Parse("", &cfg); err != nil {
   238  						t.Fatalf("\t%s\tShould be able to Parse arguments : %s.", failed, err)
   239  					}
   240  					t.Logf("\t%s\tShould be able to Parse arguments.", success)
   241  
   242  					if diff := cmp.Diff(tt.want, cfg); diff != "" {
   243  						t.Fatalf("\t%s\tShould have properly initialized struct value\n%s", failed, diff)
   244  					}
   245  					t.Logf("\t%s\tShould have properly initialized struct value.", success)
   246  				}
   247  
   248  				t.Run(tt.name, f)
   249  			}
   250  		}
   251  	}
   252  }
   253  
   254  func TestParse_Args(t *testing.T) {
   255  	t.Log("Given the need to capture remaining command line arguments after flags.")
   256  	{
   257  		type configArgs struct {
   258  			Port int
   259  			Args conf.Args
   260  		}
   261  
   262  		want := configArgs{
   263  			Port: 9000,
   264  			Args: conf.Args{"migrate", "seed"},
   265  		}
   266  
   267  		os.Args = []string{"conf.test", "--port", "9000", "migrate", "seed"}
   268  
   269  		var cfg configArgs
   270  		if _, err := conf.Parse("TEST", &cfg); err != nil {
   271  			t.Fatalf("\t%s\tShould be able to Parse arguments : %s.", failed, err)
   272  		}
   273  		t.Logf("\t%s\tShould be able to Parse arguments.", success)
   274  
   275  		if diff := cmp.Diff(want, cfg); diff != "" {
   276  			t.Fatalf("\t%s\tShould have properly initialized struct value\n%s", failed, diff)
   277  		}
   278  		t.Logf("\t%s\tShould have properly initialized struct value.", success)
   279  	}
   280  }
   281  
   282  func TestErrors(t *testing.T) {
   283  	t.Log("Given the need to validate errors that can occur with Parse.")
   284  	{
   285  		t.Logf("\tTest: %d\tWhen passing bad values to Parse.", 0)
   286  		{
   287  			f := func(t *testing.T) {
   288  				os.Args = []string{"conf.test"}
   289  
   290  				var cfg struct {
   291  					TestInt    int
   292  					TestString string
   293  					TestBool   bool
   294  				}
   295  				_, err := conf.Parse("TEST", cfg)
   296  				if err == nil {
   297  					t.Fatalf("\t%s\tShould NOT be able to accept a value by value.", failed)
   298  				}
   299  				t.Logf("\t%s\tShould NOT be able to accept a value by value : %s", success, err)
   300  			}
   301  			t.Run("not-by-ref", f)
   302  
   303  			f = func(t *testing.T) {
   304  				os.Args = []string{"conf.test"}
   305  
   306  				var cfg []string
   307  				_, err := conf.Parse("TEST", &cfg)
   308  				if err == nil {
   309  					t.Fatalf("\t%s\tShould NOT be able to pass anything but a struct value.", failed)
   310  				}
   311  				t.Logf("\t%s\tShould NOT be able to pass anything but a struct value : %s", success, err)
   312  			}
   313  			t.Run("not-struct-value", f)
   314  		}
   315  
   316  		t.Logf("\tTest: %d\tWhen bad tags to Parse.", 1)
   317  		{
   318  			f := func(t *testing.T) {
   319  				os.Args = []string{"conf.test"}
   320  
   321  				var cfg struct {
   322  					TestInt    int `conf:"default:"`
   323  					TestString string
   324  					TestBool   bool
   325  				}
   326  				_, err := conf.Parse("TEST", &cfg)
   327  				if err == nil {
   328  					t.Fatalf("\t%s\tShould NOT be able to accept tag missing value.", failed)
   329  				}
   330  				t.Logf("\t%s\tShould NOT be able to accept tag missing value : %s", success, err)
   331  			}
   332  			t.Run("tag-missing-value", f)
   333  
   334  			f = func(t *testing.T) {
   335  				os.Args = []string{"conf.test"}
   336  
   337  				var cfg struct {
   338  					TestInt    int `conf:"short:ab"`
   339  					TestString string
   340  					TestBool   bool
   341  				}
   342  				_, err := conf.Parse("TEST", &cfg)
   343  				if err == nil {
   344  					t.Fatalf("\t%s\tShould NOT be able to accept invalid short tag.", failed)
   345  				}
   346  				t.Logf("\t%s\tShould NOT be able to accept invalid short tag : %s", success, err)
   347  			}
   348  			t.Run("tag-bad-short", f)
   349  		}
   350  	}
   351  }
   352  
   353  var withNamespace = `Usage: conf.test [options] [arguments]
   354  
   355  OPTIONS
   356    --an-int/$TEST_AN_INT              <int>                 (default: 9)
   357    --a-string/-s/$TEST_A_STRING       <string>              (default: B)
   358    --bool/$TEST_BOOL                  <bool>                
   359    --ip-name/$TEST_IP_NAME_VAR        <string>              (default: localhost)
   360    --ip-ip/$TEST_IP_IP                <string>              (default: 127.0.0.0)
   361    --ip-endpoints/$TEST_IP_ENDPOINTS  <string>,[string...]  (default: 127.0.0.1:200;127.0.0.1:829)
   362    --debug-host/$TEST_DEBUG_HOST      <string>              (default: http://user:password@0.0.0.0:4000)
   363    --password/$TEST_PASSWORD          <string>              (default: password)
   364    --custom/$TEST_CUSTOM              <value>               (default: hello)
   365    --name/$TEST_NAME                  <string>              (default: bill)
   366    --e-dur/-d/$TEST_DURATION          <duration>            (default: 1s)
   367    --help/-h                          
   368    display this help message`
   369  
   370  var emptyNamespace = `Usage: conf.test [options] [arguments]
   371  
   372  OPTIONS
   373    --an-int/$AN_INT              <int>                 (default: 9)
   374    --a-string/-s/$A_STRING       <string>              (default: B)
   375    --bool/$BOOL                  <bool>                
   376    --ip-name/$IP_NAME_VAR        <string>              (default: localhost)
   377    --ip-ip/$IP_IP                <string>              (default: 127.0.0.0)
   378    --ip-endpoints/$IP_ENDPOINTS  <string>,[string...]  (default: 127.0.0.1:200;127.0.0.1:829)
   379    --debug-host/$DEBUG_HOST      <string>              (default: http://user:password@0.0.0.0:4000)
   380    --password/$PASSWORD          <string>              (default: password)
   381    --custom/$CUSTOM              <value>               (default: hello)
   382    --name/$NAME                  <string>              (default: bill)
   383    --e-dur/-d/$DURATION          <duration>            (default: 1s)
   384    --help/-h                     
   385    display this help message`
   386  
   387  var withNamespaceOptions = `Usage: conf.test [options] [arguments]
   388  
   389  OPTIONS
   390    --port/$TEST_PORT  <int>  
   391    --help/-h          
   392    display this help message`
   393  
   394  var emptyNamespaceOptions = `Usage: conf.test [options] [arguments]
   395  
   396  OPTIONS
   397    --port/$PORT  <int>  
   398    --help/-h     
   399    display this help message`
   400  
   401  func TestUsage(t *testing.T) {
   402  	tests := []struct {
   403  		name      string
   404  		namespace string
   405  		envs      map[string]string
   406  		want      string
   407  		options   string
   408  	}{
   409  		{
   410  			name:      "with-namespace",
   411  			namespace: "TEST",
   412  			envs:      map[string]string{"TEST_AN_INT": "1", "TEST_A_STRING": "s", "TEST_BOOL": "TRUE", "TEST_SKIP": "SKIP", "TEST_IP_NAME_VAR": "local", "TEST_NAME": "andy", "TEST_DURATION": "1m"},
   413  			want:      withNamespace,
   414  			options:   withNamespaceOptions,
   415  		},
   416  		{
   417  			name:      "empty-namespace",
   418  			namespace: "",
   419  			envs:      map[string]string{"AN_INT": "1", "A_STRING": "s", "BOOL": "TRUE", "SKIP": "SKIP", "IP_NAME_VAR": "local", "NAME": "andy", "DURATION": "1m"},
   420  			want:      emptyNamespace,
   421  			options:   emptyNamespaceOptions,
   422  		},
   423  	}
   424  
   425  	t.Log("Given the need validate usage output.")
   426  	{
   427  		for testID, tt := range tests {
   428  			f := func(t *testing.T) {
   429  				t.Logf("\tTest: %d\tWhen testing %s", testID, tt.name)
   430  				{
   431  					os.Clearenv()
   432  					for k, v := range tt.envs {
   433  						os.Setenv(k, v)
   434  					}
   435  
   436  					os.Args = []string{"conf.test"}
   437  
   438  					var cfg config
   439  					if _, err := conf.Parse(tt.namespace, &cfg); err != nil {
   440  						fmt.Print(err)
   441  						return
   442  					}
   443  
   444  					got, err := conf.UsageInfo(tt.namespace, &cfg)
   445  					if err != nil {
   446  						fmt.Print(err)
   447  						return
   448  					}
   449  
   450  					got = strings.TrimRight(got, " \n")
   451  					t.Log(got)
   452  					gotS := strings.Split(got, "\n")
   453  					wantS := strings.Split(tt.want, "\n")
   454  					if diff := cmp.Diff(gotS, wantS); diff != "" {
   455  						t.Errorf("\t%s\tShould match the output byte for byte. See diff:", failed)
   456  						t.Log(diff)
   457  					}
   458  					t.Logf("\t%s\tShould match byte for byte the output.", success)
   459  				}
   460  
   461  				t.Logf("\tTest: %d\tWhen using a struct with arguments.", 1)
   462  				{
   463  					var cfg struct {
   464  						Port int
   465  						Args conf.Args
   466  					}
   467  
   468  					got, err := conf.UsageInfo(tt.namespace, &cfg)
   469  					if err != nil {
   470  						fmt.Print(err)
   471  						return
   472  					}
   473  
   474  					got = strings.TrimRight(got, " \n")
   475  					gotS := strings.Split(got, "\n")
   476  					wantS := strings.Split(tt.options, "\n")
   477  					if diff := cmp.Diff(gotS, wantS); diff != "" {
   478  						t.Errorf("\t%s\tShould match the output byte for byte. See diff:", failed)
   479  						t.Log(diff)
   480  					}
   481  					t.Logf("\t%s\tShould match byte for byte the output.", success)
   482  				}
   483  			}
   484  
   485  			t.Run(tt.name, f)
   486  		}
   487  	}
   488  }
   489  
   490  func ExampleString() {
   491  	tt := struct {
   492  		name string
   493  		envs map[string]string
   494  	}{
   495  		name: "one-example",
   496  		envs: map[string]string{"TEST_AN_INT": "1", "TEST_S": "s", "TEST_BOOL": "TRUE", "TEST_SKIP": "SKIP", "TEST_IP_NAME": "local", "TEST_NAME": "andy", "TEST_DURATION": "1m"},
   497  	}
   498  
   499  	os.Clearenv()
   500  	for k, v := range tt.envs {
   501  		os.Setenv(k, v)
   502  	}
   503  
   504  	os.Args = []string{"conf.test"}
   505  
   506  	var cfg config
   507  	if _, err := conf.Parse("TEST", &cfg); err != nil {
   508  		fmt.Print(err)
   509  		return
   510  	}
   511  
   512  	out, err := conf.String(&cfg)
   513  	if err != nil {
   514  		fmt.Print(err)
   515  		return
   516  	}
   517  
   518  	fmt.Print(out)
   519  
   520  	// Output:
   521  	// --an-int=1
   522  	// --a-string/-s=B
   523  	// --bool=true
   524  	// --ip-name=localhost
   525  	// --ip-ip=127.0.0.0
   526  	// --ip-endpoints=[127.0.0.1:200 127.0.0.1:829]
   527  	// --debug-host=http://xxxxxx:xxxxxx@0.0.0.0:4000
   528  	// --password=xxxxxx
   529  	// --custom=@hello@
   530  	// --name=andy
   531  	// --e-dur/-d=1m0s
   532  }
   533  
   534  type ConfExplicit struct {
   535  	Version conf.Version
   536  	Address string
   537  }
   538  
   539  type ConfImplicit struct {
   540  	conf.Version
   541  	Address string
   542  }
   543  
   544  func TestVersionExplicit(t *testing.T) {
   545  	tests := []struct {
   546  		name    string
   547  		config  ConfExplicit
   548  		args    []string
   549  		want    string
   550  		wantErr bool
   551  	}{
   552  		{
   553  			name: "version",
   554  			args: []string{"--version"},
   555  			config: ConfExplicit{
   556  				Version: conf.Version{
   557  					Build: "v1.0.0",
   558  				},
   559  			},
   560  			wantErr: false,
   561  			want:    "Version: v1.0.0",
   562  		},
   563  		{
   564  			name: "vershort",
   565  			args: []string{"conf.test", "-v"},
   566  			config: ConfExplicit{
   567  				Version: conf.Version{
   568  					Build: "v1.0.0",
   569  				},
   570  			},
   571  			wantErr: false,
   572  			want:    "Version: v1.0.0",
   573  		},
   574  		{
   575  			name: "verdes",
   576  			args: []string{"conf.test", "-version"},
   577  			config: ConfExplicit{
   578  				Version: conf.Version{
   579  					Build: "v1.0.0",
   580  					Desc:  "Service Description",
   581  				},
   582  			},
   583  			wantErr: false,
   584  			want:    "Version: v1.0.0\nService Description",
   585  		},
   586  		{
   587  			name: "verdesshort",
   588  			args: []string{"conf.test", "-v"},
   589  			config: ConfExplicit{
   590  				Version: conf.Version{
   591  					Build: "v1.0.0",
   592  					Desc:  "Service Description",
   593  				},
   594  			},
   595  			wantErr: false,
   596  			want:    "Version: v1.0.0\nService Description",
   597  		},
   598  		{
   599  			name: "desshort",
   600  			args: []string{"conf.test", "-v"},
   601  			config: ConfExplicit{
   602  				Version: conf.Version{
   603  					Desc: "Service Description",
   604  				},
   605  			},
   606  			wantErr: false,
   607  			want:    "Service Description",
   608  		},
   609  		{
   610  			name:    "none",
   611  			args:    []string{"conf.test", "-v"},
   612  			config:  ConfExplicit{},
   613  			want:    "",
   614  			wantErr: false,
   615  		},
   616  	}
   617  
   618  	t.Log("Given the need validate version output.")
   619  	{
   620  		for i, tt := range tests {
   621  			t.Logf("\tTest: %d\tWhen using an explict struct.", i)
   622  			{
   623  				f := func(t *testing.T) {
   624  					os.Args = tt.args
   625  					if help, err := conf.Parse("APP", &tt.config); err != nil {
   626  						if err == conf.ErrHelpWanted {
   627  							if diff := cmp.Diff(tt.want, help); diff != "" {
   628  								t.Errorf("\t%s\tShould match the output byte for byte. See diff:", failed)
   629  								t.Log(diff)
   630  							}
   631  							t.Logf("\t%s\tShould match byte for byte the output.", success)
   632  						}
   633  					}
   634  				}
   635  
   636  				t.Run(tt.name, f)
   637  			}
   638  		}
   639  	}
   640  }
   641  
   642  func TestVersionImplicit(t *testing.T) {
   643  	tests := []struct {
   644  		name    string
   645  		config  ConfImplicit
   646  		args    []string
   647  		want    string
   648  		wantErr bool
   649  	}{
   650  		{
   651  			name: "only version",
   652  			args: []string{"conf.test", "--version"},
   653  			config: ConfImplicit{
   654  				Version: conf.Version{
   655  					Build: "v1.0.0",
   656  				},
   657  			},
   658  			wantErr: false,
   659  			want:    "Version: v1.0.0",
   660  		},
   661  		{
   662  			name: "only version shortcut",
   663  			args: []string{"conf.test", "-v"},
   664  			config: ConfImplicit{
   665  				Version: conf.Version{
   666  					Build: "v1.0.0",
   667  				},
   668  			},
   669  			wantErr: false,
   670  			want:    "Version: v1.0.0",
   671  		},
   672  		{
   673  			name: "version and description",
   674  			args: []string{"conf.test", "-version"},
   675  			config: ConfImplicit{
   676  				Version: conf.Version{
   677  					Build: "v1.0.0",
   678  					Desc:  "Service Description",
   679  				},
   680  			},
   681  			wantErr: false,
   682  			want:    "Version: v1.0.0\nService Description",
   683  		},
   684  		{
   685  			name: "version and description shortcut",
   686  			args: []string{"conf.test", "-v"},
   687  			config: ConfImplicit{
   688  				Version: conf.Version{
   689  					Build: "v1.0.0",
   690  					Desc:  "Service Description",
   691  				},
   692  			},
   693  			wantErr: false,
   694  			want:    "Version: v1.0.0\nService Description",
   695  		},
   696  		{
   697  			name: "only description shortcut",
   698  			args: []string{"conf.test", "-v"},
   699  			config: ConfImplicit{
   700  				Version: conf.Version{
   701  					Desc: "Service Description",
   702  				},
   703  			},
   704  			wantErr: false,
   705  			want:    "Service Description",
   706  		},
   707  		{
   708  			name:    "no version",
   709  			args:    []string{"conf.test", "-v"},
   710  			config:  ConfImplicit{},
   711  			want:    "",
   712  			wantErr: false,
   713  		},
   714  	}
   715  
   716  	t.Log("Given the need validate version output.")
   717  	{
   718  		for i, tt := range tests {
   719  			t.Logf("\tTest: %d\tWhen using an emplicit struct with.", i)
   720  			{
   721  				f := func(t *testing.T) {
   722  					os.Args = tt.args
   723  					if help, err := conf.Parse("APP", &tt.config); err != nil {
   724  						if err == conf.ErrHelpWanted {
   725  							if diff := cmp.Diff(tt.want, help); diff != "" {
   726  								t.Errorf("\t%s\tShould match the output byte for byte. See diff:", failed)
   727  								t.Log(diff)
   728  							}
   729  							t.Logf("\t%s\tShould match byte for byte the output.", success)
   730  						}
   731  					}
   732  				}
   733  
   734  				t.Run(tt.name, f)
   735  			}
   736  		}
   737  	}
   738  }
   739  
   740  // =============================================================================
   741  
   742  var yamlData = `
   743  a: Easy!
   744  b:
   745    c: 2
   746    d: [3, 4]
   747  `
   748  
   749  type internal struct {
   750  	RenamedC int   `yaml:"c"`
   751  	D        []int `yaml:",flow"`
   752  }
   753  type yamlConfig struct {
   754  	A string
   755  	B internal
   756  	E string `conf:"default:postgres"`
   757  }
   758  
   759  func TestYAML(t *testing.T) {
   760  	tests := []struct {
   761  		name string
   762  		yaml []byte
   763  		envs map[string]string
   764  		args []string
   765  		want yamlConfig
   766  	}{
   767  		{
   768  			"default",
   769  			[]byte(yamlData),
   770  			nil,
   771  			nil,
   772  			yamlConfig{A: "Easy!", B: internal{RenamedC: 2, D: []int{3, 4}}, E: "postgres"},
   773  		},
   774  		{
   775  			"env",
   776  			[]byte(yamlData),
   777  			map[string]string{"TEST_A": "EnvEasy!"},
   778  			nil,
   779  			yamlConfig{A: "EnvEasy!", B: internal{RenamedC: 2, D: []int{3, 4}}, E: "postgres"},
   780  		},
   781  		{
   782  			"flag",
   783  			[]byte(yamlData),
   784  			nil,
   785  			[]string{"conf.test", "--a", "FlagEasy!"},
   786  			yamlConfig{A: "FlagEasy!", B: internal{RenamedC: 2, D: []int{3, 4}}, E: "postgres"},
   787  		},
   788  	}
   789  
   790  	t.Log("Given the need to parse basic yaml configuration.")
   791  	{
   792  		for i, tt := range tests {
   793  			t.Logf("\tTest: %d\tWhen checking with arguments %v", i, tt.args)
   794  			{
   795  				os.Clearenv()
   796  				for k, v := range tt.envs {
   797  					os.Setenv(k, v)
   798  				}
   799  
   800  				f := func(t *testing.T) {
   801  					os.Args = tt.args
   802  
   803  					var cfg yamlConfig
   804  					if _, err := conf.Parse("TEST", &cfg, yaml.WithData(tt.yaml)); err != nil {
   805  						t.Fatalf("\t%s\tShould be able to Parse arguments : %s.", failed, err)
   806  					}
   807  					t.Logf("\t%s\tShould be able to Parse arguments.", success)
   808  
   809  					if diff := cmp.Diff(tt.want, cfg); diff != "" {
   810  						t.Fatalf("\t%s\tShould have properly initialized struct value\n%s", failed, diff)
   811  					}
   812  					t.Logf("\t%s\tShould have properly initialized struct value.", success)
   813  				}
   814  
   815  				t.Run(tt.name, f)
   816  			}
   817  		}
   818  	}
   819  }