...

Source file src/k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/files/files_test.go

Documentation: k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/files

     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  }
   479  

View as plain text