github.com/containerd/nerdctl@v1.7.7/pkg/flagutil/flagutil_test.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package flagutil 18 19 import ( 20 "bufio" 21 "errors" 22 "fmt" 23 "os" 24 "reflect" 25 "runtime" 26 "sort" 27 "strings" 28 "testing" 29 30 "gotest.tools/v3/assert" 31 ) 32 33 // tmpFileWithContent will create a temp file with given content. 34 func tmpFileWithContent(t *testing.T, content string) string { 35 t.Helper() 36 tmpFile, err := os.CreateTemp(t.TempDir(), "flagutil-test") 37 if err != nil { 38 t.Fatal(err) 39 } 40 defer tmpFile.Close() 41 42 _, err = tmpFile.WriteString(content) 43 if err != nil { 44 t.Fatal(err) 45 } 46 t.Cleanup(func() { 47 _ = os.Remove(tmpFile.Name()) 48 }) 49 return tmpFile.Name() 50 } 51 52 func TestReplaceOrAppendEnvValues(t *testing.T) { 53 tests := []struct { 54 defaults []string 55 overrides []string 56 expected []string 57 }{ 58 // override defaults 59 { 60 defaults: []string{"A=default", "B=default"}, 61 overrides: []string{"A=override", "C=override"}, 62 expected: []string{"A=override", "B=default", "C=override"}, 63 }, 64 // empty defaults 65 { 66 defaults: []string{"A=default", "B=default"}, 67 overrides: []string{"A=override", "B="}, 68 expected: []string{"A=override", "B="}, 69 }, 70 // remove defaults 71 { 72 defaults: []string{"A=default", "B=default"}, 73 overrides: []string{"A=override", "B"}, 74 expected: []string{"A=override"}, 75 }, 76 } 77 78 comparator := func(s1, s2 []string) bool { 79 if len(s1) != len(s2) { 80 return false 81 } 82 sort.Slice(s1, func(i, j int) bool { 83 return s1[i] < s1[j] 84 }) 85 sort.Slice(s2, func(i, j int) bool { 86 return s2[i] < s2[j] 87 }) 88 for i, v := range s1 { 89 if v != s2[i] { 90 return false 91 } 92 } 93 return true 94 } 95 for _, tt := range tests { 96 actual := ReplaceOrAppendEnvValues(tt.defaults, tt.overrides) 97 assert.Assert(t, comparator(actual, tt.expected), fmt.Sprintf("expected: %s, actual: %s", tt.expected, actual)) 98 } 99 } 100 101 // Test TestParseEnvFileGoodFile for a env file with a few well formatted lines. 102 func TestParseEnvFileGoodFile(t *testing.T) { 103 content := `foo=bar 104 baz=quux 105 # comment 106 107 _foobar=foobaz 108 with.dots=working 109 and_underscore=working too` 110 // Adding a newline + a line with pure whitespace. 111 // This is being done like this instead of the block above 112 // because it's common for editors to trim trailing whitespace 113 // from lines, which becomes annoying since that's the 114 // exact thing we need to test. 115 content += "\n \t " 116 tmpFile := tmpFileWithContent(t, content) 117 118 lines, err := parseEnvVars([]string{tmpFile}) 119 if err != nil { 120 t.Fatal(err) 121 } 122 123 expectedLines := []string{ 124 "foo=bar", 125 "baz=quux", 126 "_foobar=foobaz", 127 "with.dots=working", 128 "and_underscore=working too", 129 } 130 131 if !reflect.DeepEqual(lines, expectedLines) { 132 t.Fatal("lines not equal to expectedLines") 133 } 134 } 135 136 // Test TestParseEnvFileEmptyFile for an empty file. 137 func TestParseEnvFileEmptyFile(t *testing.T) { 138 tmpFile := tmpFileWithContent(t, "") 139 140 paths := []string{tmpFile} 141 lines, err := parseEnvVars(paths) 142 if err != nil { 143 t.Fatal(err) 144 } 145 146 if len(lines) != 0 { 147 t.Fatal("lines not empty; expected empty") 148 } 149 } 150 151 // Test TestParseEnvFileNonExistentFile for a non existent file. 152 func TestParseEnvFileNonExistentFile(t *testing.T) { 153 _, err := parseEnvVars([]string{"foo_bar_baz"}) 154 if err == nil { 155 t.Fatal("ParseEnvFile succeeded; expected failure") 156 } 157 if _, ok := errors.Unwrap(err).(*os.PathError); !ok { 158 t.Fatalf("Expected a PathError, got [%v]", err) 159 } 160 } 161 162 // Test TestValidateEnv for the validate function's correctness. 163 func TestValidateEnv(t *testing.T) { 164 type testCase struct { 165 value string 166 expected string 167 err error 168 } 169 tests := []testCase{ 170 { 171 value: "a", 172 expected: "a", 173 }, 174 { 175 value: "something", 176 expected: "something", 177 }, 178 { 179 value: "_=a", 180 expected: "_=a", 181 }, 182 { 183 value: "env1=value1", 184 expected: "env1=value1", 185 }, 186 { 187 value: "_env1=value1", 188 expected: "_env1=value1", 189 }, 190 { 191 value: "env2=value2=value3", 192 expected: "env2=value2=value3", 193 }, 194 { 195 value: "env3=abc!qwe", 196 expected: "env3=abc!qwe", 197 }, 198 { 199 value: "env_4=value 4", 200 expected: "env_4=value 4", 201 }, 202 { 203 value: "PATH", 204 expected: fmt.Sprintf("PATH=%v", os.Getenv("PATH")), 205 }, 206 { 207 value: "=a", 208 err: fmt.Errorf("invalid environment variable: =a"), 209 }, 210 { 211 value: "PATH=", 212 expected: "PATH=", 213 }, 214 { 215 value: "PATH=something", 216 expected: "PATH=something", 217 }, 218 { 219 value: "asd!qwe", 220 expected: "asd!qwe", 221 }, 222 { 223 value: "1asd", 224 expected: "1asd", 225 }, 226 { 227 value: "123", 228 expected: "123", 229 }, 230 { 231 value: "some space", 232 expected: "some space", 233 }, 234 { 235 value: " some space before", 236 expected: " some space before", 237 }, 238 { 239 value: "some space after ", 240 expected: "some space after ", 241 }, 242 { 243 value: "=", 244 err: fmt.Errorf("invalid environment variable: ="), 245 }, 246 } 247 248 if runtime.GOOS == "windows" { 249 // Environment variables are case in-sensitive on Windows 250 tests = append(tests, testCase{ 251 value: "PaTh", 252 expected: fmt.Sprintf("PaTh=%v", os.Getenv("PATH")), 253 err: nil, 254 }) 255 } 256 257 for _, tc := range tests { 258 tc := tc 259 t.Run(tc.value, func(t *testing.T) { 260 actual, err := withOSEnv([]string{tc.value}) 261 if tc.err == nil { 262 assert.NilError(t, err) 263 } else { 264 assert.Error(t, err, tc.err.Error()) 265 } 266 if actual != nil { 267 v := actual[0] 268 assert.Equal(t, v, tc.expected) 269 } 270 }) 271 } 272 } 273 274 // Test TestParseEnvFileLineTooLongFile for a file with a line exceeding bufio.MaxScanTokenSize. 275 func TestParseEnvFileLineTooLongFile(t *testing.T) { 276 content := "foo=" + strings.Repeat("a", bufio.MaxScanTokenSize+42) 277 tmpFile := tmpFileWithContent(t, content) 278 279 _, err := MergeEnvFileAndOSEnv([]string{tmpFile}, nil) 280 if err == nil { 281 t.Fatal("ParseEnvFile succeeded; expected failure") 282 } 283 } 284 285 // Test TestParseEnvVariableWithNoNameFile for parsing env file with empty variable name. 286 func TestParseEnvVariableWithNoNameFile(t *testing.T) { 287 content := `# comment= 288 =blank variable names are an error case 289 ` 290 tmpFile := tmpFileWithContent(t, content) 291 292 _, err := MergeEnvFileAndOSEnv([]string{tmpFile}, nil) 293 if nil == err { 294 t.Fatal("if a variable has no name parsing an environment file must fail") 295 } 296 } 297 298 // Test TestMergeEnvFileAndOSEnv for merging variables from env-file and env. 299 func TestMergeEnvFileAndOSEnv(t *testing.T) { 300 content := `HOME` 301 tmpFile := tmpFileWithContent(t, content) 302 303 variables, err := MergeEnvFileAndOSEnv([]string{tmpFile}, []string{"PATH"}) 304 if nil != err { 305 t.Fatalf("unexpected error: %v", err) 306 } 307 308 if len(variables) != 2 { 309 t.Fatal("variables from env-file and flag env should be merged") 310 } 311 312 if "HOME="+os.Getenv("HOME") != variables[0] { 313 t.Fatal("the HOME variable is not properly imported as the first variable") 314 } 315 316 if "PATH="+os.Getenv("PATH") != variables[1] { 317 t.Fatal("the PATH variable is not properly imported as the second variable") 318 } 319 }