k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/kubeletconfig/util/files/files_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes 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 files 18 19 import ( 20 "fmt" 21 "os" 22 "path/filepath" 23 "testing" 24 25 utiltest "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/test" 26 utilfs "k8s.io/kubernetes/pkg/util/filesystem" 27 ) 28 29 const ( 30 prefix = "test-util-files" 31 ) 32 33 type file struct { 34 name string 35 // mode distinguishes file type, 36 // we only check for regular vs. directory in these tests, 37 // specify regular as 0, directory as os.ModeDir 38 mode os.FileMode 39 data string // ignored if mode == os.ModeDir 40 } 41 42 func (f *file) write(fs utilfs.Filesystem, dir string) error { 43 path := filepath.Join(dir, f.name) 44 if f.mode.IsDir() { 45 if err := fs.MkdirAll(path, defaultPerm); err != nil { 46 return err 47 } 48 } else if f.mode.IsRegular() { 49 // create parent directories, if necessary 50 parents := filepath.Dir(path) 51 if err := fs.MkdirAll(parents, defaultPerm); err != nil { 52 return err 53 } 54 // create the file 55 handle, err := fs.Create(path) 56 if err != nil { 57 return err 58 } 59 _, err = handle.Write([]byte(f.data)) 60 // The file should always be closed, not just in error cases. 61 if cerr := handle.Close(); cerr != nil { 62 return fmt.Errorf("error closing file: %v", cerr) 63 } 64 if err != nil { 65 return err 66 } 67 } else { 68 return fmt.Errorf("mode not implemented for testing %s", f.mode.String()) 69 } 70 return nil 71 } 72 73 func (f *file) expect(fs utilfs.Filesystem, dir string) error { 74 path := filepath.Join(dir, f.name) 75 if f.mode.IsDir() { 76 info, err := fs.Stat(path) 77 if err != nil { 78 return err 79 } 80 if !info.IsDir() { 81 return fmt.Errorf("expected directory, got mode %s", info.Mode().String()) 82 } 83 } else if f.mode.IsRegular() { 84 info, err := fs.Stat(path) 85 if err != nil { 86 return err 87 } 88 if !info.Mode().IsRegular() { 89 return fmt.Errorf("expected regular file, got mode %s", info.Mode().String()) 90 } 91 data, err := fs.ReadFile(path) 92 if err != nil { 93 return err 94 } 95 if f.data != string(data) { 96 return fmt.Errorf("expected file data %q, got %q", f.data, string(data)) 97 } 98 } else { 99 return fmt.Errorf("mode not implemented for testing %s", f.mode.String()) 100 } 101 return nil 102 } 103 104 // write files, perform some function, then attempt to read files back 105 // if err is non-empty, expects an error from the function performed in the test 106 // and skips reading back the expected files 107 type test struct { 108 desc string 109 writes []file 110 expects []file 111 fn func(fs utilfs.Filesystem, dir string, c *test) []error 112 err string 113 } 114 115 func (c *test) write(t *testing.T, fs utilfs.Filesystem, dir string) { 116 for _, f := range c.writes { 117 if err := f.write(fs, dir); err != nil { 118 t.Fatalf("error pre-writing file: %v", err) 119 } 120 } 121 } 122 123 // you can optionally skip calling t.Errorf by passing a nil t, and process the 124 // returned errors instead 125 func (c *test) expect(t *testing.T, fs utilfs.Filesystem, dir string) []error { 126 errs := []error{} 127 for _, f := range c.expects { 128 if err := f.expect(fs, dir); err != nil { 129 msg := fmt.Errorf("expect %#v, got error: %v", f, err) 130 errs = append(errs, msg) 131 if t != nil { 132 t.Errorf("%s", msg) 133 } 134 } 135 } 136 return errs 137 } 138 139 // run a test case, with an arbitrary function to execute between write and expect 140 // if c.fn is nil, errors from c.expect are checked against c.err, instead of errors 141 // from fn being checked against c.err 142 func (c *test) run(t *testing.T, fs utilfs.Filesystem) { 143 // isolate each test case in a new temporary directory 144 dir, err := fs.TempDir("", prefix) 145 if err != nil { 146 t.Fatalf("error creating temporary directory for test: %v", err) 147 } 148 defer os.RemoveAll(dir) 149 c.write(t, fs, dir) 150 // if fn exists, check errors from fn, then check expected files 151 if c.fn != nil { 152 errs := c.fn(fs, dir, c) 153 if len(errs) > 0 { 154 for _, err := range errs { 155 utiltest.ExpectError(t, err, c.err) 156 } 157 // skip checking expected files if we expected errors 158 // (usually means we didn't create file) 159 return 160 } 161 c.expect(t, fs, dir) 162 return 163 } 164 // just check expected files, and compare errors from c.expect to c.err 165 // (this lets us test the helper functions above) 166 errs := c.expect(nil, fs, dir) 167 for _, err := range errs { 168 utiltest.ExpectError(t, err, c.err) 169 } 170 } 171 172 // simple test of the above helper functions 173 func TestHelpers(t *testing.T) { 174 // omitting the test.fn means test.err is compared to errors from test.expect 175 cases := []test{ 176 { 177 desc: "regular file", 178 writes: []file{{name: "foo", data: "bar"}}, 179 expects: []file{{name: "foo", data: "bar"}}, 180 }, 181 { 182 desc: "directory", 183 writes: []file{{name: "foo", mode: os.ModeDir}}, 184 expects: []file{{name: "foo", mode: os.ModeDir}}, 185 }, 186 { 187 desc: "deep regular file", 188 writes: []file{{name: "foo/bar", data: "baz"}}, 189 expects: []file{{name: "foo/bar", data: "baz"}}, 190 }, 191 { 192 desc: "deep directory", 193 writes: []file{{name: "foo/bar", mode: os.ModeDir}}, 194 expects: []file{{name: "foo/bar", mode: os.ModeDir}}, 195 }, 196 { 197 desc: "missing file", 198 expects: []file{{name: "foo", data: "bar"}}, 199 err: missingFileError, 200 }, 201 { 202 desc: "missing directory", 203 expects: []file{{name: "foo/bar", mode: os.ModeDir}}, 204 err: missingFolderError, 205 }, 206 } 207 for _, c := range cases { 208 t.Run(c.desc, func(t *testing.T) { 209 c.run(t, &utilfs.DefaultFs{}) 210 }) 211 } 212 } 213 214 func TestFileExists(t *testing.T) { 215 fn := func(fs utilfs.Filesystem, dir string, c *test) []error { 216 ok, err := FileExists(fs, filepath.Join(dir, "foo")) 217 if err != nil { 218 return []error{err} 219 } 220 if !ok { 221 return []error{fmt.Errorf("does not exist (test)")} 222 } 223 return nil 224 } 225 cases := []test{ 226 { 227 fn: fn, 228 desc: "file exists", 229 writes: []file{{name: "foo"}}, 230 }, 231 { 232 fn: fn, 233 desc: "file does not exist", 234 err: "does not exist (test)", 235 }, 236 { 237 fn: fn, 238 desc: "object has non-file mode", 239 writes: []file{{name: "foo", mode: os.ModeDir}}, 240 err: "expected regular file", 241 }, 242 } 243 for _, c := range cases { 244 t.Run(c.desc, func(t *testing.T) { 245 c.run(t, &utilfs.DefaultFs{}) 246 }) 247 } 248 } 249 250 func TestEnsureFile(t *testing.T) { 251 fn := func(fs utilfs.Filesystem, dir string, c *test) []error { 252 var errs []error 253 for _, f := range c.expects { 254 if err := EnsureFile(fs, filepath.Join(dir, f.name)); err != nil { 255 errs = append(errs, err) 256 } 257 } 258 return errs 259 } 260 cases := []test{ 261 { 262 fn: fn, 263 desc: "file exists", 264 writes: []file{{name: "foo"}}, 265 expects: []file{{name: "foo"}}, 266 }, 267 { 268 fn: fn, 269 desc: "file does not exist", 270 expects: []file{{name: "bar"}}, 271 }, 272 { 273 fn: fn, 274 desc: "neither parent nor file exists", 275 expects: []file{{name: "baz/quux"}}, 276 }, 277 } 278 for _, c := range cases { 279 t.Run(c.desc, func(t *testing.T) { 280 c.run(t, &utilfs.DefaultFs{}) 281 }) 282 } 283 } 284 285 // Note: This transitively tests WriteTmpFile 286 func TestReplaceFile(t *testing.T) { 287 fn := func(fs utilfs.Filesystem, dir string, c *test) []error { 288 var errs []error 289 for _, f := range c.expects { 290 if err := ReplaceFile(fs, filepath.Join(dir, f.name), []byte(f.data)); err != nil { 291 errs = append(errs, err) 292 } 293 } 294 return errs 295 } 296 cases := []test{ 297 { 298 fn: fn, 299 desc: "file exists", 300 writes: []file{{name: "foo"}}, 301 expects: []file{{name: "foo", data: "bar"}}, 302 }, 303 { 304 fn: fn, 305 desc: "file does not exist", 306 expects: []file{{name: "foo", data: "bar"}}, 307 }, 308 { 309 fn: func(fs utilfs.Filesystem, dir string, c *test) []error { 310 if err := ReplaceFile(fs, filepath.Join(dir, "foo/bar"), []byte("")); err != nil { 311 return []error{err} 312 } 313 return nil 314 }, 315 desc: "neither parent nor file exists", 316 err: missingFolderError, 317 }, 318 } 319 for _, c := range cases { 320 t.Run(c.desc, func(t *testing.T) { 321 c.run(t, &utilfs.DefaultFs{}) 322 }) 323 } 324 } 325 326 func TestDirExists(t *testing.T) { 327 fn := func(fs utilfs.Filesystem, dir string, c *test) []error { 328 ok, err := DirExists(fs, filepath.Join(dir, "foo")) 329 if err != nil { 330 return []error{err} 331 } 332 if !ok { 333 return []error{fmt.Errorf("does not exist (test)")} 334 } 335 return nil 336 } 337 cases := []test{ 338 { 339 fn: fn, 340 desc: "dir exists", 341 writes: []file{{name: "foo", mode: os.ModeDir}}, 342 }, 343 { 344 fn: fn, 345 desc: "dir does not exist", 346 err: "does not exist (test)", 347 }, 348 { 349 fn: fn, 350 desc: "object has non-dir mode", 351 writes: []file{{name: "foo"}}, 352 err: "expected dir", 353 }, 354 } 355 for _, c := range cases { 356 t.Run(c.desc, func(t *testing.T) { 357 c.run(t, &utilfs.DefaultFs{}) 358 }) 359 } 360 } 361 362 func TestEnsureDir(t *testing.T) { 363 fn := func(fs utilfs.Filesystem, dir string, c *test) []error { 364 var errs []error 365 for _, f := range c.expects { 366 if err := EnsureDir(fs, filepath.Join(dir, f.name)); err != nil { 367 errs = append(errs, err) 368 } 369 } 370 return errs 371 } 372 cases := []test{ 373 { 374 fn: fn, 375 desc: "dir exists", 376 writes: []file{{name: "foo", mode: os.ModeDir}}, 377 expects: []file{{name: "foo", mode: os.ModeDir}}, 378 }, 379 { 380 fn: fn, 381 desc: "dir does not exist", 382 expects: []file{{name: "bar", mode: os.ModeDir}}, 383 }, 384 { 385 fn: fn, 386 desc: "neither parent nor dir exists", 387 expects: []file{{name: "baz/quux", mode: os.ModeDir}}, 388 }, 389 } 390 for _, c := range cases { 391 t.Run(c.desc, func(t *testing.T) { 392 c.run(t, &utilfs.DefaultFs{}) 393 }) 394 } 395 } 396 397 func TestWriteTempDir(t *testing.T) { 398 // writing a tmp dir is covered by TestReplaceDir, but we additionally test filename validation here 399 c := test{ 400 desc: "invalid file key", 401 err: "invalid file key", 402 fn: func(fs utilfs.Filesystem, dir string, c *test) []error { 403 if _, err := WriteTempDir(fs, filepath.Join(dir, "tmpdir"), map[string]string{"foo/bar": ""}); err != nil { 404 return []error{err} 405 } 406 return nil 407 }, 408 } 409 c.run(t, &utilfs.DefaultFs{}) 410 } 411 412 func TestReplaceDir(t *testing.T) { 413 fn := func(fs utilfs.Filesystem, dir string, c *test) []error { 414 errs := []error{} 415 416 // compute filesets from expected files and call ReplaceDir for each 417 // we don't nest dirs in test cases, order of ReplaceDir call is not guaranteed 418 dirs := map[string]map[string]string{} 419 420 // allocate dirs 421 for _, f := range c.expects { 422 if f.mode.IsDir() { 423 path := filepath.Join(dir, f.name) 424 if _, ok := dirs[path]; !ok { 425 dirs[path] = map[string]string{} 426 } 427 } else if f.mode.IsRegular() { 428 path := filepath.Join(dir, filepath.Dir(f.name)) 429 if _, ok := dirs[path]; !ok { 430 // require an expectation for the parent directory if there is an expectation for the file 431 errs = append(errs, fmt.Errorf("no prior parent directory in c.expects for file %s", f.name)) 432 continue 433 } 434 dirs[path][filepath.Base(f.name)] = f.data 435 } 436 } 437 438 // short-circuit test case validation errors 439 if len(errs) > 0 { 440 return errs 441 } 442 443 // call ReplaceDir for each desired dir 444 for path, files := range dirs { 445 if err := ReplaceDir(fs, path, files); err != nil { 446 errs = append(errs, err) 447 } 448 } 449 return errs 450 } 451 cases := []test{ 452 { 453 fn: fn, 454 desc: "fn catches invalid test case", 455 expects: []file{{name: "foo/bar"}}, 456 err: "no prior parent directory", 457 }, 458 { 459 fn: fn, 460 desc: "empty dir", 461 expects: []file{{name: "foo", mode: os.ModeDir}}, 462 }, 463 { 464 fn: fn, 465 desc: "dir with files", 466 expects: []file{ 467 {name: "foo", mode: os.ModeDir}, 468 {name: "foo/bar", data: "baz"}, 469 {name: "foo/baz", data: "bar"}, 470 }, 471 }, 472 } 473 for _, c := range cases { 474 t.Run(c.desc, func(t *testing.T) { 475 c.run(t, &utilfs.DefaultFs{}) 476 }) 477 } 478 }