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 }