code.cestus.io/tools/fabricator@v0.4.3/pkg/ff/parse_test.go (about)

     1  package ff_test
     2  
     3  import (
     4  	"os"
     5  	"testing"
     6  	"time"
     7  
     8  	"code.cestus.io/tools/fabricator/pkg/ff"
     9  	"code.cestus.io/tools/fabricator/pkg/ff/fftest"
    10  )
    11  
    12  func TestParseBasics(t *testing.T) {
    13  	t.Parallel()
    14  
    15  	for _, testcase := range []struct {
    16  		name string
    17  		env  map[string]string
    18  		file string
    19  		args []string
    20  		opts []ff.Option
    21  		want fftest.Vars
    22  	}{
    23  		{
    24  			name: "empty",
    25  			args: []string{},
    26  			want: fftest.Vars{},
    27  		},
    28  		{
    29  			name: "args only",
    30  			args: []string{"-s", "foo", "-i", "123", "-b", "-d", "24m"},
    31  			want: fftest.Vars{S: "foo", I: 123, B: true, D: 24 * time.Minute},
    32  		},
    33  		{
    34  			name: "file only",
    35  			file: "testdata/1.conf",
    36  			want: fftest.Vars{S: "bar", I: 99, B: true, D: time.Hour},
    37  		},
    38  		{
    39  			name: "env only",
    40  			env:  map[string]string{"TEST_PARSE_S": "baz", "TEST_PARSE_F": "0.99", "TEST_PARSE_D": "100s"},
    41  			opts: []ff.Option{ff.WithEnvVarPrefix("TEST_PARSE")},
    42  			want: fftest.Vars{S: "baz", F: 0.99, D: 100 * time.Second},
    43  		},
    44  		{
    45  			name: "file args",
    46  			file: "testdata/2.conf",
    47  			args: []string{"-s", "foo", "-i", "1234"},
    48  			want: fftest.Vars{S: "foo", I: 1234, D: 3 * time.Second},
    49  		},
    50  		{
    51  			name: "env args",
    52  			env:  map[string]string{"TEST_PARSE_S": "should be overridden", "TEST_PARSE_B": "true"},
    53  			args: []string{"-s", "explicit wins", "-i", "7"},
    54  			opts: []ff.Option{ff.WithEnvVarPrefix("TEST_PARSE")},
    55  			want: fftest.Vars{S: "explicit wins", I: 7, B: true},
    56  		},
    57  		{
    58  			name: "file env",
    59  			env:  map[string]string{"TEST_PARSE_S": "env takes priority", "TEST_PARSE_B": "true"},
    60  			file: "testdata/3.conf",
    61  			opts: []ff.Option{ff.WithEnvVarPrefix("TEST_PARSE")},
    62  			want: fftest.Vars{S: "env takes priority", I: 99, B: true, D: 34 * time.Second},
    63  		},
    64  		{
    65  			name: "file env args",
    66  			file: "testdata/4.conf",
    67  			env:  map[string]string{"TEST_PARSE_S": "from env", "TEST_PARSE_I": "300", "TEST_PARSE_F": "0.15", "TEST_PARSE_B": "true"},
    68  			args: []string{"-s", "from arg", "-i", "100"},
    69  			opts: []ff.Option{ff.WithEnvVarPrefix("TEST_PARSE")},
    70  			want: fftest.Vars{S: "from arg", I: 100, F: 0.15, B: true, D: time.Minute},
    71  		},
    72  		{
    73  			name: "repeated args",
    74  			args: []string{"-s", "foo", "-s", "bar", "-d", "1m", "-d", "1h", "-x", "1", "-x", "2", "-x", "3"},
    75  			want: fftest.Vars{S: "bar", D: time.Hour, X: []string{"1", "2", "3"}},
    76  		},
    77  		{
    78  			name: "priority repeats",
    79  			env:  map[string]string{"TEST_PARSE_S": "s.env", "TEST_PARSE_X": "x.env.1"},
    80  			file: "testdata/5.conf",
    81  			args: []string{"-s", "s.arg.1", "-s", "s.arg.2", "-x", "x.arg.1", "-x", "x.arg.2"},
    82  			opts: []ff.Option{ff.WithEnvVarPrefix("TEST_PARSE")},
    83  			want: fftest.Vars{S: "s.arg.2", X: []string{"x.arg.1", "x.arg.2"}}, // highest prio wins and no others are called
    84  		},
    85  		{
    86  			name: "PlainParser solo bool",
    87  			file: "testdata/solo_bool.conf",
    88  			want: fftest.Vars{S: "x", B: true},
    89  		},
    90  		{
    91  			name: "PlainParser string with spaces",
    92  			file: "testdata/spaces.conf",
    93  			want: fftest.Vars{S: "i am the very model of a modern major general"},
    94  		},
    95  		{
    96  			name: "default comma behavior",
    97  			env:  map[string]string{"TEST_PARSE_S": "one,two,three", "TEST_PARSE_X": "one,two,three"},
    98  			opts: []ff.Option{ff.WithEnvVarPrefix("TEST_PARSE")},
    99  			want: fftest.Vars{S: "one,two,three", X: []string{"one,two,three"}},
   100  		},
   101  		{
   102  			name: "WithEnvVarSplit",
   103  			env:  map[string]string{"TEST_PARSE_S": "one,two,three", "TEST_PARSE_X": "one,two,three"},
   104  			opts: []ff.Option{ff.WithEnvVarPrefix("TEST_PARSE"), ff.WithEnvVarSplit(",")},
   105  			want: fftest.Vars{S: "three", X: []string{"one", "two", "three"}},
   106  		},
   107  		{
   108  			name: "WithEnvVarNoPrefix",
   109  			env:  map[string]string{"TEST_PARSE_S": "foo", "S": "bar"},
   110  			opts: []ff.Option{ff.WithEnvVarNoPrefix()},
   111  			want: fftest.Vars{S: "bar"},
   112  		},
   113  		{
   114  			name: "WithIgnoreUndefined env",
   115  			env:  map[string]string{"TEST_PARSE_UNDEFINED": "one", "TEST_PARSE_S": "one"},
   116  			opts: []ff.Option{ff.WithEnvVarPrefix("TEST_PARSE"), ff.WithIgnoreUndefined(true)},
   117  			want: fftest.Vars{S: "one"},
   118  		},
   119  		{
   120  			name: "WithIgnoreUndefined file true",
   121  			file: "testdata/undefined.conf",
   122  			opts: []ff.Option{ff.WithIgnoreUndefined(true)},
   123  			want: fftest.Vars{S: "one"},
   124  		},
   125  		{
   126  			name: "WithIgnoreUndefined file false",
   127  			file: "testdata/undefined.conf",
   128  			opts: []ff.Option{ff.WithIgnoreUndefined(false)},
   129  			want: fftest.Vars{WantParseErrorString: "config file flag"},
   130  		},
   131  		{
   132  			name: "env var split comma whitespace",
   133  			env:  map[string]string{"TEST_PARSE_S": "one, two, three ", "TEST_PARSE_X": "one, two, three "},
   134  			opts: []ff.Option{ff.WithEnvVarPrefix("TEST_PARSE"), ff.WithEnvVarSplit(",")},
   135  			want: fftest.Vars{S: " three ", X: []string{"one", " two", " three "}},
   136  		},
   137  	} {
   138  		t.Run(testcase.name, func(t *testing.T) {
   139  			if testcase.file != "" {
   140  				testcase.opts = append(testcase.opts, ff.WithConfigFile(testcase.file), ff.WithConfigFileParser(ff.PlainParser))
   141  			}
   142  
   143  			if len(testcase.env) > 0 {
   144  				for k, v := range testcase.env {
   145  					defer os.Setenv(k, os.Getenv(k))
   146  					os.Setenv(k, v)
   147  				}
   148  			}
   149  
   150  			fs, vars := fftest.Pair()
   151  			vars.ParseError = ff.Parse(fs, testcase.args, testcase.opts...)
   152  			if err := fftest.Compare(&testcase.want, vars); err != nil {
   153  				t.Fatal(err)
   154  			}
   155  		})
   156  	}
   157  }
   158  
   159  func TestParseIssue16(t *testing.T) {
   160  	t.Parallel()
   161  
   162  	for _, testcase := range []struct {
   163  		name string
   164  		data string
   165  		want string
   166  	}{
   167  		{
   168  			name: "hash in value",
   169  			data: "s bar#baz",
   170  			want: "bar#baz",
   171  		},
   172  		{
   173  			name: "EOL comment with space",
   174  			data: "s bar # baz",
   175  			want: "bar",
   176  		},
   177  		{
   178  			name: "EOL comment no space",
   179  			data: "s bar #baz",
   180  			want: "bar",
   181  		},
   182  		{
   183  			name: "only comment with space",
   184  			data: "# foo bar\n",
   185  			want: "",
   186  		},
   187  		{
   188  			name: "only comment no space",
   189  			data: "#foo bar\n",
   190  			want: "",
   191  		},
   192  	} {
   193  		t.Run(testcase.name, func(t *testing.T) {
   194  			filename, cleanup := fftest.TempFile(t, testcase.data)
   195  			defer cleanup()
   196  
   197  			want := fftest.Vars{S: testcase.want}
   198  
   199  			fs, vars := fftest.Pair()
   200  			vars.ParseError = ff.Parse(fs, []string{},
   201  				ff.WithConfigFile(filename),
   202  				ff.WithConfigFileParser(ff.PlainParser),
   203  			)
   204  
   205  			if err := fftest.Compare(&want, vars); err != nil {
   206  				t.Fatal(err)
   207  			}
   208  		})
   209  	}
   210  }
   211  
   212  func TestParseConfigFile(t *testing.T) {
   213  	t.Parallel()
   214  
   215  	for _, testcase := range []struct {
   216  		name         string
   217  		missing      bool
   218  		allowMissing bool
   219  		parseError   error
   220  	}{
   221  		{
   222  			name: "has config file",
   223  		},
   224  		{
   225  			name:       "config file missing",
   226  			missing:    true,
   227  			parseError: os.ErrNotExist,
   228  		},
   229  		{
   230  			name:         "config file missing + allow missing",
   231  			missing:      true,
   232  			allowMissing: true,
   233  		},
   234  	} {
   235  		t.Run(testcase.name, func(t *testing.T) {
   236  			filename := "dummy"
   237  			if !testcase.missing {
   238  				var cleanup func()
   239  				filename, cleanup = fftest.TempFile(t, "")
   240  				defer cleanup()
   241  			}
   242  
   243  			options := []ff.Option{ff.WithConfigFile(filename), ff.WithConfigFileParser(ff.PlainParser)}
   244  			if testcase.allowMissing {
   245  				options = append(options, ff.WithAllowMissingConfigFile(true))
   246  			}
   247  
   248  			want := fftest.Vars{WantParseErrorIs: testcase.parseError}
   249  
   250  			fs, vars := fftest.Pair()
   251  			vars.ParseError = ff.Parse(fs, []string{}, options...)
   252  
   253  			if err := fftest.Compare(&want, vars); err != nil {
   254  				t.Fatal(err)
   255  			}
   256  		})
   257  	}
   258  }