From d578f50d5ec200a7af83186b282a22cceb927f1b Mon Sep 17 00:00:00 2001 From: DCCooper <1866858@gmail.com> Date: Thu, 28 Oct 2021 22:41:18 +0800 Subject: [PATCH 08/16] util: add unit test for file.go Signed-off-by: DCCooper <1866858@gmail.com> --- util/file.go | 42 +++- util/file_test.go | 547 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 578 insertions(+), 11 deletions(-) create mode 100644 util/file_test.go diff --git a/util/file.go b/util/file.go index cd4a75d5..e0353898 100644 --- a/util/file.go +++ b/util/file.go @@ -29,12 +29,26 @@ const ( fileMaxSize = 10 * 1024 * 1024 // 10MB ) +var ( + modifyTime = time.Date(2017, time.January, 0, 0, 0, 0, 0, time.UTC) + accessTime = time.Date(2017, time.January, 0, 0, 0, 0, 0, time.UTC) +) + // ReadSmallFile read small file less than 10MB func ReadSmallFile(path string) ([]byte, error) { st, err := os.Lstat(path) if err != nil { return nil, err } + + if !st.Mode().IsRegular() { + return nil, errors.Errorf("loading file %s should be a regular file", st.Name()) + } + + if st.Size() == 0 { + return nil, errors.New("loading file is empty") + } + if st.Size() > fileMaxSize { return nil, errors.Errorf("file %q too big", path) } @@ -51,18 +65,18 @@ func LoadJSONFile(file string, v interface{}) error { } // ChangeDirModifyTime changes modify time of directory -func ChangeDirModifyTime(dir string) error { +func ChangeDirModifyTime(dir string, accessTime, modifyTime time.Time) error { fs, rErr := ioutil.ReadDir(dir) if rErr != nil { return rErr } for _, f := range fs { src := filepath.Join(dir, f.Name()) - if err := ChangeFileModifyTime(src); err != nil { + if err := ChangeFileModifyTime(src, accessTime, modifyTime); err != nil { return err } if f.IsDir() { - if err := ChangeDirModifyTime(src); err != nil { + if err := ChangeDirModifyTime(src, accessTime, modifyTime); err != nil { return err } } @@ -71,13 +85,11 @@ func ChangeDirModifyTime(dir string) error { } // ChangeFileModifyTime changes modify time of file by fixing time at 2017-01-01 00:00:00 -func ChangeFileModifyTime(path string) error { - mtime := time.Date(2017, time.January, 0, 0, 0, 0, 0, time.UTC) - atime := time.Date(2017, time.January, 0, 0, 0, 0, 0, time.UTC) +func ChangeFileModifyTime(path string, accessTime, modifyTime time.Time) error { if _, err := os.Lstat(path); err != nil { return err } - if err := os.Chtimes(path, atime, mtime); err != nil { + if err := os.Chtimes(path, accessTime, modifyTime); err != nil { return err } return nil @@ -87,9 +99,9 @@ func ChangeFileModifyTime(path string) error { // by using different compression method defined by "com" // the files' modify time attribute will be set to a fix time "2017-01-01 00:00:00" // if set "modifyTime" to true -func PackFiles(src, dest string, com archive.Compression, modifyTime bool) (err error) { - if modifyTime { - if err = ChangeDirModifyTime(src); err != nil { +func PackFiles(src, dest string, com archive.Compression, needModifyTime bool) (err error) { + if needModifyTime { + if err = ChangeDirModifyTime(src, accessTime, modifyTime); err != nil { return err } } @@ -122,6 +134,14 @@ func PackFiles(src, dest string, com archive.Compression, modifyTime bool) (err // by using different compression method defined by "com" // The src file will be remove if set "rm" to true func UnpackFile(src, dest string, com archive.Compression, rm bool) (err error) { + if len(dest) == 0 { + return errors.New("unpack: dest path should not be empty") + } + d, err := os.Stat(dest) + if err != nil || !d.IsDir() { + return errors.Wrapf(err, "unpack: invalid dest path") + } + cleanPath := filepath.Clean(src) f, err := os.Open(cleanPath) // nolint:gosec if err != nil { @@ -139,7 +159,7 @@ func UnpackFile(src, dest string, com archive.Compression, rm bool) (err error) return errors.Wrapf(err, "unpack file %q failed", src) } - if err = ChangeDirModifyTime(dest); err != nil { + if err = ChangeDirModifyTime(dest, modifyTime, accessTime); err != nil { return errors.Wrapf(err, "change modify time for directory %q failed", dest) } diff --git a/util/file_test.go b/util/file_test.go new file mode 100644 index 00000000..09aed41d --- /dev/null +++ b/util/file_test.go @@ -0,0 +1,547 @@ +// Copyright (c) Huawei Technologies Co., Ltd. 2020. All rights reserved. +// isula-build licensed under the Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. +// Author: Xiang Li +// Create: 2021-08-24 +// Description: file manipulation related common functions + +package util + +import ( + "encoding/json" + "io/ioutil" + "os" + "path/filepath" + "reflect" + "testing" + "time" + + "github.com/containers/storage/pkg/archive" + "gotest.tools/v3/assert" + "gotest.tools/v3/fs" + constant "isula.org/isula-build" +) + +func TestReadSmallFile(t *testing.T) { + smallFile := fs.NewFile(t, t.Name()) + defer smallFile.Remove() + err := ioutil.WriteFile(smallFile.Path(), []byte("small file"), constant.DefaultRootFileMode) + assert.NilError(t, err) + + root := fs.NewDir(t, t.Name()) + defer root.Remove() + + bigFile := filepath.Join(root.Path(), "bigFile") + f, err := os.Create(bigFile) + assert.NilError(t, err) + defer os.Remove(f.Name()) + err = f.Truncate(fileMaxSize + 1) + assert.NilError(t, err) + + emptyFile := fs.NewFile(t, t.Name()) + defer emptyFile.Remove() + + type args struct { + path string + } + tests := []struct { + name string + args args + want []byte + wantErr bool + }{ + { + name: "TC-normal read", + args: args{path: smallFile.Path()}, + want: []byte("small file"), + }, + { + name: "TC-not exist path", + wantErr: true, + }, + { + name: "TC-file too big", + args: args{path: bigFile}, + wantErr: true, + }, + { + name: "TC-empty file", + args: args{path: emptyFile.Path()}, + wantErr: true, + }, + { + name: "TC-invalid file", + args: args{path: "/dev/cdrom"}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ReadSmallFile(tt.args.path) + if (err != nil) != tt.wantErr { + t.Errorf("ReadSmallFile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ReadSmallFile() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLoadJSONFile(t *testing.T) { + type rename struct { + Name string `json:"name"` + Rename string `json:"rename"` + } + type args struct { + file string + v rename + } + + smallJSONFile := fs.NewFile(t, t.Name()) + defer smallJSONFile.Remove() + validData := rename{ + Name: "origin name", + Rename: "modified name", + } + b, err := json.Marshal(validData) + assert.NilError(t, err) + ioutil.WriteFile(smallJSONFile.Path(), b, constant.DefaultRootFileMode) + + tests := []struct { + name string + args args + wantKey string + wantValue string + wantErr bool + }{ + { + name: "TC-normal json file", + args: args{ + file: smallJSONFile.Path(), + v: rename{}, + }, + wantKey: "origin name", + wantValue: "modified name", + }, + { + name: "TC-json file not exist", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := LoadJSONFile(tt.args.file, &tt.args.v); (err != nil) != tt.wantErr { + t.Errorf("LoadJSONFile() error = %v, wantErr %v", err, tt.wantErr) + } + if err == nil { + assert.Equal(t, tt.args.v.Name, tt.wantKey) + assert.Equal(t, tt.args.v.Rename, tt.wantValue) + } + }) + } +} + +func TestChangeFileModifyTime(t *testing.T) { + normalFile := fs.NewFile(t, t.Name()) + defer normalFile.Remove() + + pwd, err := os.Getwd() + assert.NilError(t, err) + immutableFile := filepath.Join(pwd, "immutableFile") + _, err = os.Create(immutableFile) + defer os.Remove(immutableFile) + + type args struct { + path string + mtime time.Time + atime time.Time + } + tests := []struct { + name string + args args + wantErr bool + needHook bool + preHookFun func(t *testing.T) + postHookFun func(t *testing.T) + }{ + { + name: "TC-change file modify time", + args: args{ + path: immutableFile, + mtime: modifyTime, + atime: accessTime, + }, + }, + { + name: "TC-file path empty", + wantErr: true, + }, + { + name: "TC-lack of permession", + args: args{ + path: immutableFile, + atime: accessTime, + mtime: modifyTime, + }, + needHook: true, + preHookFun: func(t *testing.T) { Immutable(immutableFile, true) }, + postHookFun: func(t *testing.T) { Immutable(immutableFile, false) }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.needHook { + tt.preHookFun(t) + } + err := ChangeFileModifyTime(tt.args.path, tt.args.atime, tt.args.mtime) + if tt.needHook { + defer tt.postHookFun(t) + } + if (err != nil) != tt.wantErr { + t.Errorf("ChangeFileModifyTime() error = %v, wantErr %v", err, tt.wantErr) + } + if err == nil { + f, err := os.Stat(tt.args.path) + assert.NilError(t, err) + assert.Equal(t, true, f.ModTime().Equal(modifyTime)) + } + }) + } +} + +type tempDirs struct { + root string + subDir1 string + subDir11 string + file1 string + file11 string +} + +func createDirs(t *testing.T) tempDirs { + pwd, err := os.Getwd() + assert.NilError(t, err) + + root := filepath.Join(pwd, t.Name()) + assert.NilError(t, os.Mkdir(root, constant.DefaultRootDirMode)) + + rootSubDir1 := filepath.Join(root, "rootSubDir1") + assert.NilError(t, os.Mkdir(rootSubDir1, constant.DefaultRootDirMode)) + + rootSubDir11 := filepath.Join(rootSubDir1, "rootSubDir11") + assert.NilError(t, os.Mkdir(rootSubDir11, constant.DefaultRootDirMode)) + + file1 := filepath.Join(rootSubDir1, "file1") + _, err = os.Create(file1) + assert.NilError(t, err) + + file11 := filepath.Join(rootSubDir11, "file11") + _, err = os.Create(file11) + assert.NilError(t, err) + + return tempDirs{ + root: root, + subDir1: rootSubDir1, + subDir11: rootSubDir11, + file1: file1, + file11: file11, + } +} + +func (tmp *tempDirs) removeAll(t *testing.T) { + assert.NilError(t, os.RemoveAll(tmp.root)) + assert.NilError(t, os.RemoveAll(tmp.subDir1)) + assert.NilError(t, os.RemoveAll(tmp.subDir11)) + assert.NilError(t, os.RemoveAll(tmp.file1)) + assert.NilError(t, os.RemoveAll(tmp.file11)) +} + +func TestChangeDirModifyTime(t *testing.T) { + tempDirs := createDirs(t) + defer tempDirs.removeAll(t) + root := tempDirs.root + + type args struct { + dir string + mtime time.Time + atime time.Time + } + tests := []struct { + name string + args args + wantErr bool + needPreHook bool + needPostHook bool + preWalkFun func(path string, info os.FileInfo, err error) error + postWalkFun func(path string, info os.FileInfo, err error) error + }{ + { + name: "TC-normal case modify directory", + args: args{ + dir: root, + mtime: modifyTime, + atime: accessTime, + }, + needPostHook: true, + postWalkFun: func(path string, info os.FileInfo, err error) error { + assert.Assert(t, true, info.ModTime().Equal(modifyTime)) + return nil + }, + }, + { + name: "TC-empty path", + wantErr: true, + }, + { + name: "TC-lack of permission", + args: args{ + dir: root, + mtime: modifyTime, + atime: accessTime, + }, + wantErr: true, + needPreHook: true, + needPostHook: true, + preWalkFun: func(path string, info os.FileInfo, err error) error { + if !info.IsDir() { + Immutable(path, true) + } + return nil + }, + postWalkFun: func(path string, info os.FileInfo, err error) error { + if !info.IsDir() { + Immutable(path, false) + } + return nil + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.needPreHook { + wErr := filepath.Walk(tt.args.dir, tt.preWalkFun) + assert.NilError(t, wErr) + } + err := ChangeDirModifyTime(tt.args.dir, tt.args.mtime, tt.args.atime) + if (err != nil) != tt.wantErr { + t.Errorf("ChangeDirModifyTime() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.needPostHook { + wErr := filepath.Walk(tt.args.dir, tt.postWalkFun) + assert.NilError(t, wErr) + } + }) + } +} + +func TestPackFiles(t *testing.T) { + dirs := createDirs(t) + defer dirs.removeAll(t) + dest := fs.NewFile(t, t.Name()) + defer dest.Remove() + + type args struct { + src string + dest string + com archive.Compression + needModifyTime bool + } + tests := []struct { + name string + args args + wantErr bool + needPreHook bool + needPostHook bool + preWalkFun func(path string, info os.FileInfo, err error) error + postWalkFun func(path string, info os.FileInfo, err error) error + }{ + { + name: "TC-normal pack", + args: args{ + src: dirs.root, + dest: dest.Path(), + com: archive.Gzip, + needModifyTime: true, + }, + }, + { + name: "TC-empty dest", + args: args{ + src: dirs.root, + com: archive.Gzip, + needModifyTime: true, + }, + wantErr: true, + }, + { + name: "TC-invalid compression", + args: args{ + src: dirs.root, + dest: dest.Path(), + com: archive.Compression(-1), + needModifyTime: true, + }, + wantErr: true, + }, + { + name: "TC-lack of permission", + args: args{ + src: dirs.root, + dest: dest.Path(), + com: archive.Gzip, + needModifyTime: true, + }, + wantErr: true, + needPreHook: true, + needPostHook: true, + preWalkFun: func(path string, info os.FileInfo, err error) error { + if !info.IsDir() { + Immutable(path, true) + } + return nil + }, + postWalkFun: func(path string, info os.FileInfo, err error) error { + if !info.IsDir() { + Immutable(path, false) + } + return nil + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.needPreHook { + wErr := filepath.Walk(tt.args.src, tt.preWalkFun) + assert.NilError(t, wErr) + } + if err := PackFiles(tt.args.src, tt.args.dest, tt.args.com, tt.args.needModifyTime); (err != nil) != tt.wantErr { + t.Errorf("PackFiles() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.needPostHook { + wErr := filepath.Walk(tt.args.src, tt.postWalkFun) + assert.NilError(t, wErr) + } + }) + } +} + +func TestUnpackFile(t *testing.T) { + folderToBePacked := createDirs(t) + defer folderToBePacked.removeAll(t) + pwd, err := os.Getwd() + assert.NilError(t, err) + + tarName := filepath.Join(pwd, "test.tar") + assert.NilError(t, PackFiles(folderToBePacked.root, tarName, archive.Gzip, true)) + defer os.RemoveAll(tarName) + + invalidTar := filepath.Join(pwd, "invalid.tar") + err = ioutil.WriteFile(invalidTar, []byte("invalid tar"), constant.DefaultRootFileMode) + assert.NilError(t, err) + defer os.RemoveAll(invalidTar) + + unpackDest := filepath.Join(pwd, "unpack") + assert.NilError(t, os.MkdirAll(unpackDest, constant.DefaultRootDirMode)) + defer os.RemoveAll(unpackDest) + + type args struct { + src string + dest string + com archive.Compression + rm bool + } + tests := []struct { + name string + args args + needPreHook bool + needPostHook bool + wantErr bool + }{ + { + name: "normal unpack file", + args: args{ + src: tarName, + dest: unpackDest, + com: archive.Gzip, + rm: true, + }, + }, + { + name: "empty unpack destation path", + args: args{ + src: tarName, + com: archive.Gzip, + rm: false, + }, + wantErr: true, + }, + { + name: "unpack src path not exist", + args: args{ + src: "path not exist", + dest: unpackDest, + com: archive.Gzip, + rm: false, + }, + wantErr: true, + }, + { + name: "unpack destation path not exist", + args: args{ + src: tarName, + dest: "path not exist", + com: archive.Gzip, + rm: false, + }, + wantErr: true, + }, + { + name: "invalid tarball", + args: args{ + src: invalidTar, + dest: unpackDest, + com: archive.Gzip, + rm: false, + }, + wantErr: true, + }, + { + name: "no permission for src", + args: args{ + src: tarName, + dest: unpackDest, + com: archive.Gzip, + rm: true, + }, + wantErr: true, + needPreHook: true, + needPostHook: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.needPreHook { + assert.NilError(t, Immutable(tt.args.src, true)) + } + err := UnpackFile(tt.args.src, tt.args.dest, tt.args.com, tt.args.rm) + if (err != nil) != tt.wantErr { + t.Errorf("UnpackFile() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.needPostHook { + assert.NilError(t, Immutable(tt.args.src, false)) + } + if tt.args.rm && err == nil { + tarName := filepath.Join(pwd, "test.tar") + assert.NilError(t, PackFiles(folderToBePacked.root, tarName, archive.Gzip, true)) + } + }) + } +} -- 2.27.0