mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-26 05:03:09 +00:00
Currently, the test TestReplaceFile/neither_parent_nor_file_exists fails because the error encountered doesn't match the expected error. On Windows, if a file is missing, the encountered error is: The system cannot find the file specified. And if a folder / parent folder is missing, this error is encoutered instead: The system cannot find the path specified.
479 lines
12 KiB
Go
479 lines
12 KiB
Go
/*
|
|
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))
|
|
// The file should always be closed, not just in error cases.
|
|
if cerr := handle.Close(); cerr != nil {
|
|
return fmt.Errorf("error closing file: %v", cerr)
|
|
}
|
|
if err != nil {
|
|
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)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
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: missingFileError,
|
|
},
|
|
{
|
|
desc: "missing directory",
|
|
expects: []file{{name: "foo/bar", mode: os.ModeDir}},
|
|
err: missingFolderError,
|
|
},
|
|
}
|
|
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: missingFolderError,
|
|
},
|
|
}
|
|
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{})
|
|
})
|
|
}
|
|
}
|