github.com/m3db/m3@v1.5.0/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 }