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

