123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477 |
- /*
- Copyright 2018 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package files
- import (
- "fmt"
- "os"
- "path/filepath"
- "testing"
- utiltest "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/test"
- utilfs "k8s.io/kubernetes/pkg/util/filesystem"
- )
- const (
- prefix = "test-util-files"
- )
- type file struct {
- name string
- // mode distinguishes file type,
- // we only check for regular vs. directory in these tests,
- // specify regular as 0, directory as os.ModeDir
- mode os.FileMode
- data string // ignored if mode == os.ModeDir
- }
- func (f *file) write(fs utilfs.Filesystem, dir string) error {
- path := filepath.Join(dir, f.name)
- if f.mode.IsDir() {
- if err := fs.MkdirAll(path, defaultPerm); err != nil {
- return err
- }
- } else if f.mode.IsRegular() {
- // create parent directories, if necessary
- parents := filepath.Dir(path)
- if err := fs.MkdirAll(parents, defaultPerm); err != nil {
- return err
- }
- // create the file
- handle, err := fs.Create(path)
- if err != nil {
- return err
- }
- _, err = handle.Write([]byte(f.data))
- if err != nil {
- if cerr := handle.Close(); cerr != nil {
- return fmt.Errorf("error %v closing file after error: %v", cerr, err)
- }
- return err
- }
- } else {
- return fmt.Errorf("mode not implemented for testing %s", f.mode.String())
- }
- return nil
- }
- func (f *file) expect(fs utilfs.Filesystem, dir string) error {
- path := filepath.Join(dir, f.name)
- if f.mode.IsDir() {
- info, err := fs.Stat(path)
- if err != nil {
- return err
- }
- if !info.IsDir() {
- return fmt.Errorf("expected directory, got mode %s", info.Mode().String())
- }
- } else if f.mode.IsRegular() {
- info, err := fs.Stat(path)
- if err != nil {
- return err
- }
- if !info.Mode().IsRegular() {
- return fmt.Errorf("expected regular file, got mode %s", info.Mode().String())
- }
- data, err := fs.ReadFile(path)
- if err != nil {
- return err
- }
- if f.data != string(data) {
- return fmt.Errorf("expected file data %q, got %q", f.data, string(data))
- }
- } else {
- return fmt.Errorf("mode not implemented for testing %s", f.mode.String())
- }
- return nil
- }
- // write files, perform some function, then attempt to read files back
- // if err is non-empty, expects an error from the function performed in the test
- // and skips reading back the expected files
- type test struct {
- desc string
- writes []file
- expects []file
- fn func(fs utilfs.Filesystem, dir string, c *test) []error
- err string
- }
- func (c *test) write(t *testing.T, fs utilfs.Filesystem, dir string) {
- for _, f := range c.writes {
- if err := f.write(fs, dir); err != nil {
- t.Fatalf("error pre-writing file: %v", err)
- }
- }
- }
- // you can optionally skip calling t.Errorf by passing a nil t, and process the
- // returned errors instead
- func (c *test) expect(t *testing.T, fs utilfs.Filesystem, dir string) []error {
- errs := []error{}
- for _, f := range c.expects {
- if err := f.expect(fs, dir); err != nil {
- msg := fmt.Errorf("expect %#v, got error: %v", f, err)
- errs = append(errs, msg)
- if t != nil {
- t.Errorf("%s", msg)
- }
- }
- }
- return errs
- }
- // run a test case, with an arbitrary function to execute between write and expect
- // if c.fn is nil, errors from c.expect are checked against c.err, instead of errors
- // from fn being checked against c.err
- func (c *test) run(t *testing.T, fs utilfs.Filesystem) {
- // isolate each test case in a new temporary directory
- dir, err := fs.TempDir("", prefix)
- if err != nil {
- t.Fatalf("error creating temporary directory for test: %v", err)
- }
- c.write(t, fs, dir)
- // if fn exists, check errors from fn, then check expected files
- if c.fn != nil {
- errs := c.fn(fs, dir, c)
- if len(errs) > 0 {
- for _, err := range errs {
- utiltest.ExpectError(t, err, c.err)
- }
- // skip checking expected files if we expected errors
- // (usually means we didn't create file)
- return
- }
- c.expect(t, fs, dir)
- return
- }
- // just check expected files, and compare errors from c.expect to c.err
- // (this lets us test the helper functions above)
- errs := c.expect(nil, fs, dir)
- for _, err := range errs {
- utiltest.ExpectError(t, err, c.err)
- }
- }
- // simple test of the above helper functions
- func TestHelpers(t *testing.T) {
- // omitting the test.fn means test.err is compared to errors from test.expect
- cases := []test{
- {
- desc: "regular file",
- writes: []file{{name: "foo", data: "bar"}},
- expects: []file{{name: "foo", data: "bar"}},
- },
- {
- desc: "directory",
- writes: []file{{name: "foo", mode: os.ModeDir}},
- expects: []file{{name: "foo", mode: os.ModeDir}},
- },
- {
- desc: "deep regular file",
- writes: []file{{name: "foo/bar", data: "baz"}},
- expects: []file{{name: "foo/bar", data: "baz"}},
- },
- {
- desc: "deep directory",
- writes: []file{{name: "foo/bar", mode: os.ModeDir}},
- expects: []file{{name: "foo/bar", mode: os.ModeDir}},
- },
- {
- desc: "missing file",
- expects: []file{{name: "foo", data: "bar"}},
- err: "no such file or directory",
- },
- {
- desc: "missing directory",
- expects: []file{{name: "foo/bar", mode: os.ModeDir}},
- err: "no such file or directory",
- },
- }
- for _, c := range cases {
- t.Run(c.desc, func(t *testing.T) {
- c.run(t, utilfs.DefaultFs{})
- })
- }
- }
- func TestFileExists(t *testing.T) {
- fn := func(fs utilfs.Filesystem, dir string, c *test) []error {
- ok, err := FileExists(fs, filepath.Join(dir, "foo"))
- if err != nil {
- return []error{err}
- }
- if !ok {
- return []error{fmt.Errorf("does not exist (test)")}
- }
- return nil
- }
- cases := []test{
- {
- fn: fn,
- desc: "file exists",
- writes: []file{{name: "foo"}},
- },
- {
- fn: fn,
- desc: "file does not exist",
- err: "does not exist (test)",
- },
- {
- fn: fn,
- desc: "object has non-file mode",
- writes: []file{{name: "foo", mode: os.ModeDir}},
- err: "expected regular file",
- },
- }
- for _, c := range cases {
- t.Run(c.desc, func(t *testing.T) {
- c.run(t, utilfs.DefaultFs{})
- })
- }
- }
- func TestEnsureFile(t *testing.T) {
- fn := func(fs utilfs.Filesystem, dir string, c *test) []error {
- var errs []error
- for _, f := range c.expects {
- if err := EnsureFile(fs, filepath.Join(dir, f.name)); err != nil {
- errs = append(errs, err)
- }
- }
- return errs
- }
- cases := []test{
- {
- fn: fn,
- desc: "file exists",
- writes: []file{{name: "foo"}},
- expects: []file{{name: "foo"}},
- },
- {
- fn: fn,
- desc: "file does not exist",
- expects: []file{{name: "bar"}},
- },
- {
- fn: fn,
- desc: "neither parent nor file exists",
- expects: []file{{name: "baz/quux"}},
- },
- }
- for _, c := range cases {
- t.Run(c.desc, func(t *testing.T) {
- c.run(t, utilfs.DefaultFs{})
- })
- }
- }
- // Note: This transitively tests WriteTmpFile
- func TestReplaceFile(t *testing.T) {
- fn := func(fs utilfs.Filesystem, dir string, c *test) []error {
- var errs []error
- for _, f := range c.expects {
- if err := ReplaceFile(fs, filepath.Join(dir, f.name), []byte(f.data)); err != nil {
- errs = append(errs, err)
- }
- }
- return errs
- }
- cases := []test{
- {
- fn: fn,
- desc: "file exists",
- writes: []file{{name: "foo"}},
- expects: []file{{name: "foo", data: "bar"}},
- },
- {
- fn: fn,
- desc: "file does not exist",
- expects: []file{{name: "foo", data: "bar"}},
- },
- {
- fn: func(fs utilfs.Filesystem, dir string, c *test) []error {
- if err := ReplaceFile(fs, filepath.Join(dir, "foo/bar"), []byte("")); err != nil {
- return []error{err}
- }
- return nil
- },
- desc: "neither parent nor file exists",
- err: "no such file or directory",
- },
- }
- for _, c := range cases {
- t.Run(c.desc, func(t *testing.T) {
- c.run(t, utilfs.DefaultFs{})
- })
- }
- }
- func TestDirExists(t *testing.T) {
- fn := func(fs utilfs.Filesystem, dir string, c *test) []error {
- ok, err := DirExists(fs, filepath.Join(dir, "foo"))
- if err != nil {
- return []error{err}
- }
- if !ok {
- return []error{fmt.Errorf("does not exist (test)")}
- }
- return nil
- }
- cases := []test{
- {
- fn: fn,
- desc: "dir exists",
- writes: []file{{name: "foo", mode: os.ModeDir}},
- },
- {
- fn: fn,
- desc: "dir does not exist",
- err: "does not exist (test)",
- },
- {
- fn: fn,
- desc: "object has non-dir mode",
- writes: []file{{name: "foo"}},
- err: "expected dir",
- },
- }
- for _, c := range cases {
- t.Run(c.desc, func(t *testing.T) {
- c.run(t, utilfs.DefaultFs{})
- })
- }
- }
- func TestEnsureDir(t *testing.T) {
- fn := func(fs utilfs.Filesystem, dir string, c *test) []error {
- var errs []error
- for _, f := range c.expects {
- if err := EnsureDir(fs, filepath.Join(dir, f.name)); err != nil {
- errs = append(errs, err)
- }
- }
- return errs
- }
- cases := []test{
- {
- fn: fn,
- desc: "dir exists",
- writes: []file{{name: "foo", mode: os.ModeDir}},
- expects: []file{{name: "foo", mode: os.ModeDir}},
- },
- {
- fn: fn,
- desc: "dir does not exist",
- expects: []file{{name: "bar", mode: os.ModeDir}},
- },
- {
- fn: fn,
- desc: "neither parent nor dir exists",
- expects: []file{{name: "baz/quux", mode: os.ModeDir}},
- },
- }
- for _, c := range cases {
- t.Run(c.desc, func(t *testing.T) {
- c.run(t, utilfs.DefaultFs{})
- })
- }
- }
- func TestWriteTempDir(t *testing.T) {
- // writing a tmp dir is covered by TestReplaceDir, but we additionally test filename validation here
- c := test{
- desc: "invalid file key",
- err: "invalid file key",
- fn: func(fs utilfs.Filesystem, dir string, c *test) []error {
- if _, err := WriteTempDir(fs, filepath.Join(dir, "tmpdir"), map[string]string{"foo/bar": ""}); err != nil {
- return []error{err}
- }
- return nil
- },
- }
- c.run(t, utilfs.DefaultFs{})
- }
- func TestReplaceDir(t *testing.T) {
- fn := func(fs utilfs.Filesystem, dir string, c *test) []error {
- errs := []error{}
- // compute filesets from expected files and call ReplaceDir for each
- // we don't nest dirs in test cases, order of ReplaceDir call is not guaranteed
- dirs := map[string]map[string]string{}
- // allocate dirs
- for _, f := range c.expects {
- if f.mode.IsDir() {
- path := filepath.Join(dir, f.name)
- if _, ok := dirs[path]; !ok {
- dirs[path] = map[string]string{}
- }
- } else if f.mode.IsRegular() {
- path := filepath.Join(dir, filepath.Dir(f.name))
- if _, ok := dirs[path]; !ok {
- // require an expectation for the parent directory if there is an expectation for the file
- errs = append(errs, fmt.Errorf("no prior parent directory in c.expects for file %s", f.name))
- continue
- }
- dirs[path][filepath.Base(f.name)] = f.data
- }
- }
- // short-circuit test case validation errors
- if len(errs) > 0 {
- return errs
- }
- // call ReplaceDir for each desired dir
- for path, files := range dirs {
- if err := ReplaceDir(fs, path, files); err != nil {
- errs = append(errs, err)
- }
- }
- return errs
- }
- cases := []test{
- {
- fn: fn,
- desc: "fn catches invalid test case",
- expects: []file{{name: "foo/bar"}},
- err: "no prior parent directory",
- },
- {
- fn: fn,
- desc: "empty dir",
- expects: []file{{name: "foo", mode: os.ModeDir}},
- },
- {
- fn: fn,
- desc: "dir with files",
- expects: []file{
- {name: "foo", mode: os.ModeDir},
- {name: "foo/bar", data: "baz"},
- {name: "foo/baz", data: "bar"},
- },
- },
- }
- for _, c := range cases {
- t.Run(c.desc, func(t *testing.T) {
- c.run(t, utilfs.DefaultFs{})
- })
- }
- }
|