github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/x/config/configflag/flag_test.go (about)

     1  // Copyright (c) 2019 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package configflag
    22  
    23  import (
    24  	"bytes"
    25  	"flag"
    26  	"testing"
    27  
    28  	"github.com/golang/mock/gomock"
    29  	"github.com/stretchr/testify/assert"
    30  	"github.com/stretchr/testify/require"
    31  	"gopkg.in/yaml.v2"
    32  
    33  	"github.com/m3db/m3/src/x/config"
    34  )
    35  
    36  type testConfig struct {
    37  	Foo int    `yaml:"foo"`
    38  	Bar string `yaml:"bar"`
    39  }
    40  
    41  func TestCommandLineOptions(t *testing.T) {
    42  	type testContext struct {
    43  		Flags       flag.FlagSet
    44  		Opts        Options
    45  		MockOS      *MockosIface
    46  		UsageCalled bool
    47  	}
    48  
    49  	setup := func(t *testing.T) (*testContext, func()) {
    50  		ctrl := gomock.NewController(t)
    51  		mockOS := NewMockosIface(ctrl)
    52  
    53  		tctx := &testContext{
    54  			Opts:   Options{osFns: mockOS},
    55  			MockOS: mockOS,
    56  		}
    57  
    58  		tctx.Flags.Usage = func() {
    59  			tctx.UsageCalled = true
    60  		}
    61  
    62  		tctx.Opts.RegisterFlagSet(&tctx.Flags)
    63  
    64  		return tctx, ctrl.Finish
    65  	}
    66  
    67  	expectedConfig := testConfig{Foo: 1, Bar: "bar"}
    68  	configFileOpts := []string{"-f", "./testdata/config1.yaml", "-f", "./testdata/config2.yaml"}
    69  
    70  	t.Run("loads config from files", func(t *testing.T) {
    71  		tctx, teardown := setup(t)
    72  		defer teardown()
    73  
    74  		require.NoError(t, tctx.Flags.Parse(configFileOpts))
    75  
    76  		var cfg testConfig
    77  		require.NoError(t, tctx.Opts.MainLoad(&cfg, config.Options{}))
    78  		assert.Equal(t, expectedConfig, cfg)
    79  	})
    80  
    81  	t.Run("dumps config and exits on d", func(t *testing.T) {
    82  		tctx, teardown := setup(t)
    83  		defer teardown()
    84  
    85  		stdout := &bytes.Buffer{}
    86  
    87  		tctx.MockOS.EXPECT().Exit(0).Times(1)
    88  		tctx.MockOS.EXPECT().Stdout().Return(stdout)
    89  
    90  		args := append([]string{"-d"}, configFileOpts...)
    91  		require.NoError(t, tctx.Flags.Parse(args))
    92  
    93  		var cfg testConfig
    94  		require.NoError(t, tctx.Opts.MainLoad(&cfg, config.Options{}))
    95  
    96  		var actual testConfig
    97  		require.NoError(t, yaml.NewDecoder(bytes.NewReader(stdout.Bytes())).Decode(&actual))
    98  		assert.Equal(t, expectedConfig, actual)
    99  	})
   100  
   101  	t.Run("errors on no configs", func(t *testing.T) {
   102  		tctx, teardown := setup(t)
   103  		defer teardown()
   104  
   105  		require.NoError(t, tctx.Flags.Parse(nil))
   106  
   107  		require.EqualError(t,
   108  			tctx.Opts.MainLoad(nil, config.Options{}),
   109  			"-f is required (no config files provided)")
   110  		assert.True(t, tctx.UsageCalled)
   111  	})
   112  }
   113  
   114  func TestFlagArray(t *testing.T) {
   115  	tests := []struct {
   116  		args     []string
   117  		defaults FlagStringSlice
   118  		name     string
   119  		want     string
   120  	}{
   121  		{
   122  			name: "single value",
   123  			args: []string{"-f", "./some/file/path/here.yaml"},
   124  			want: "[./some/file/path/here.yaml]",
   125  		},
   126  		{
   127  			name: "single empty value",
   128  			args: []string{"-f", ""},
   129  			want: "[]",
   130  		},
   131  		{
   132  			name: "two value",
   133  			args: []string{"-f", "file1.yaml", "-f", "file2.yaml"},
   134  			want: "[file1.yaml file2.yaml]",
   135  		},
   136  		{
   137  			name: "two value one of which empty",
   138  			args: []string{"-f", "", "-f", "file2.yaml"},
   139  			want: "[ file2.yaml]",
   140  		},
   141  		{
   142  			name: "three values",
   143  			args: []string{"-f", "file1.yaml", "-f", "file2.yaml", "-f", "file3.yaml"},
   144  			want: "[file1.yaml file2.yaml file3.yaml]",
   145  		},
   146  		{
   147  			name:     "default and no flag",
   148  			args:     nil,
   149  			want:     "[file1.yaml]",
   150  			defaults: FlagStringSlice{Value: []string{"file1.yaml"}},
   151  		},
   152  		{
   153  			name:     "default is overridden by flag",
   154  			args:     []string{"-f", "override.yaml"},
   155  			want:     "[override.yaml]",
   156  			defaults: FlagStringSlice{Value: []string{"file1.yaml"}},
   157  		},
   158  	}
   159  
   160  	for _, tt := range tests {
   161  		t.Run(tt.name, func(t *testing.T) {
   162  			fs := flag.NewFlagSet(tt.name, flag.PanicOnError)
   163  			fs.Var(&tt.defaults, "f", "config files")
   164  			err := fs.Parse(tt.args)
   165  			require.NoError(t, err, "error parsing flags")
   166  			assert.Equal(t, tt.want, tt.defaults.String(), "unexpected output")
   167  		})
   168  	}
   169  }
   170  
   171  func TestFlagStringSliceNilFlag(t *testing.T) {
   172  	var s *FlagStringSlice
   173  	assert.Equal(t, "", s.String(), "nil string slice representation")
   174  }
   175  
   176  func TestFlagStringSliceWithOtherFlags(t *testing.T) {
   177  	var values FlagStringSlice
   178  	var x string
   179  	fs := flag.NewFlagSet("app", flag.PanicOnError)
   180  	fs.StringVar(&x, "x", "", "some random var")
   181  	fs.Var(&values, "f", "config files")
   182  	require.NoError(t, fs.Parse([]string{"-f", "file1.yaml", "-x", "file2.yaml", "-f", "file3.yaml"}))
   183  	assert.Equal(t, "[file1.yaml file3.yaml]", values.String(), "flag string slice representation")
   184  	assert.Equal(t, "file2.yaml", x, "x value")
   185  }