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 process
import (
"bufio"
"context"
"encoding/json"
"errors"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"reflect"
"strings"
"testing"
"time"
"github.com/agiledragon/gomonkey/v2"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
"ascend-common/api"
"ascend-docker-runtime/mindxcheckutils"
)
const (
pidSample = 123
fileMode0600 os.FileMode = 0600
ascendVisibleDeviceTestStr = api.AscendVisibleDevicesEnv + "=0-3,5,7"
configFile = "config.json"
strRepeatTimes = 129
testStr = "test"
)
var (
containerConfigInputStream = os.Stdin
testError = errors.New("test")
)
func TestDoPrestartHookCase1(t *testing.T) {
err := DoPrestartHook()
assert.NotNil(t, err)
}
func TestDoPrestartHookCase2(t *testing.T) {
conCfg := containerConfig{
Pid: pidSample,
Rootfs: ".",
Env: []string{api.AscendVisibleDevicesEnv + "=0l-3,5,7"},
}
patch := gomonkey.ApplyFuncReturn(getContainerConfig, &conCfg, nil)
defer patch.Reset()
err := DoPrestartHook()
assert.NotNil(t, err)
}
func TestDoPrestartHookCase3(t *testing.T) {
conCfg := containerConfig{
Pid: pidSample,
Rootfs: ".",
Env: []string{"ASCEND_VISIBLE_DEVICE=0-3,5,7"},
}
patch := gomonkey.ApplyFuncReturn(getContainerConfig, &conCfg, nil)
defer patch.Reset()
err := DoPrestartHook()
assert.Nil(t, err)
}
func TestDoPrestartHookCase4(t *testing.T) {
conCfg := containerConfig{
Pid: pidSample,
Rootfs: ".",
Env: []string{
api.AscendVisibleDevicesEnv + "=0",
api.AscendRuntimeOptionsEnv + "=VIRTUAL,NODRV",
},
}
err := InitLogModule(context.Background())
if err != nil {
t.Log("failed")
}
patch := gomonkey.ApplyFuncReturn(getContainerConfig, &conCfg, nil).
ApplyGlobalVar(&ascendDockerCliName, "").
ApplyFuncReturn(doExec, nil)
defer patch.Reset()
err = DoPrestartHook()
assert.NotNil(t, err)
}
func TestDoPrestartHookCase5(t *testing.T) {
defer func() {
if err := recover(); err != nil {
t.Log("exception", err)
}
}()
conCfg := containerConfig{
Pid: pidSample,
Rootfs: ".",
Env: []string{ascendVisibleDeviceTestStr},
}
patch := gomonkey.ApplyFuncReturn(getContainerConfig, &conCfg, nil).
ApplyGlobalVar(&ascendDockerCliName, "clii").
ApplyGlobalVar(&defaultAscendDockerCliName, "clii").
ApplyFuncReturn(doExec, nil)
defer patch.Reset()
err := DoPrestartHook()
assert.NotNil(t, err)
}
func TestDoPrestartHookPatch1(t *testing.T) {
convey.Convey("test DoPrestartHook patch1", t, func() {
ctrCfg := &containerConfig{}
patches := gomonkey.ApplyFuncReturn(getContainerConfig, ctrCfg, nil).
ApplyFuncReturn(getValueByKey, testStr).
ApplyFuncReturn(parseMounts, []string{testStr}).
ApplyFuncReturn(readConfigsOfDir, []string{testStr}, []string{testStr}, nil)
defer patches.Reset()
convey.Convey("01-parseRuntimeOptions error, should return error", func() {
patch := gomonkey.ApplyFuncReturn(parseRuntimeOptions, []string{testStr}, testError)
defer patch.Reset()
err := DoPrestartHook()
convey.So(err, convey.ShouldBeError)
})
patches.ApplyFuncReturn(parseRuntimeOptions, []string{testStr}, nil)
convey.Convey("02-parseSoftLinkMode error, should return error", func() {
patch := gomonkey.ApplyFuncReturn(parseSoftLinkMode, "", testError)
defer patch.Reset()
err := DoPrestartHook()
convey.So(err, convey.ShouldBeError)
})
patches.ApplyFuncReturn(parseSoftLinkMode, testStr, nil)
convey.Convey("03-Executable error, should return error", func() {
patch := gomonkey.ApplyFuncReturn(os.Executable, "", testError)
defer patch.Reset()
err := DoPrestartHook()
convey.So(err, convey.ShouldBeError)
})
patches.ApplyFuncReturn(os.Executable, testStr, nil)
convey.Convey("04-Stat error, should return error", func() {
patch := gomonkey.ApplyFuncReturn(os.Stat, fileInfoMock{}, testError)
defer patch.Reset()
err := DoPrestartHook()
convey.So(err, convey.ShouldBeError)
})
patches.ApplyFuncReturn(os.Stat, fileInfoMock{}, nil).
ApplyFuncReturn(mindxcheckutils.RealFileChecker, testStr, nil).
ApplyFuncReturn(getArgs, []string{testStr})
convey.Convey("05-ChangeRuntimeLogMode error, should return error", func() {
patch := gomonkey.ApplyFuncReturn(mindxcheckutils.ChangeRuntimeLogMode, testError)
defer patch.Reset()
err := DoPrestartHook()
convey.So(err, convey.ShouldBeError)
})
})
}
func TestDoPrestartHookPatch2(t *testing.T) {
convey.Convey("test DoPrestartHook patch2", t, func() {
ctrCfg := &containerConfig{}
patches := gomonkey.ApplyFuncReturn(getContainerConfig, ctrCfg, nil).
ApplyFuncReturn(getValueByKey, testStr).
ApplyFuncReturn(parseMounts, []string{testStr}).
ApplyFuncReturn(readConfigsOfDir, []string{testStr}, []string{testStr}, nil).
ApplyFuncReturn(parseRuntimeOptions, []string{testStr}, nil).
ApplyFuncReturn(parseSoftLinkMode, testStr, nil).
ApplyFuncReturn(os.Executable, testStr, nil).
ApplyFuncReturn(os.Stat, fileInfoMock{}, nil).
ApplyFuncReturn(mindxcheckutils.RealFileChecker, testStr, nil).
ApplyFuncReturn(getArgs, []string{testStr}).
ApplyFuncReturn(mindxcheckutils.ChangeRuntimeLogMode, nil)
defer patches.Reset()
convey.Convey("06-doExec error, should return error", func() {
patch := gomonkey.ApplyFuncReturn(doExec, testError)
defer patch.Reset()
err := DoPrestartHook()
convey.So(err, convey.ShouldBeError)
})
patches.ApplyFuncReturn(doExec, nil)
convey.Convey("07-success, should return nil", func() {
err := DoPrestartHook()
convey.So(err, convey.ShouldBeNil)
})
})
}
func TestGetValueByKeyCase1(t *testing.T) {
data := []string{ascendVisibleDeviceTestStr}
word := api.AscendVisibleDevicesEnv
expectVal := "0-3,5,7"
actualVal := getValueByKey(data, word)
if actualVal != expectVal {
t.Fail()
}
}
func TestGetValueByKeyCase2(t *testing.T) {
data := []string{api.AscendVisibleDevicesEnv}
word := api.AscendVisibleDevicesEnv
expectVal := ""
defer func() {
if err := recover(); err != nil {
t.Log("exception occur")
}
}()
actualVal := getValueByKey(data, word)
if actualVal != expectVal {
t.Fail()
}
}
func TestGetValueByKeyCase3(t *testing.T) {
data := []string{ascendVisibleDeviceTestStr}
word := "ASCEND_VISIBLE_DEVICE"
expectVal := ""
actualVal := getValueByKey(data, word)
if actualVal != expectVal {
t.Fail()
}
}
func TestParseOciSpecFileCase1(t *testing.T) {
file := "file"
_, err := parseOciSpecFile(file)
if err == nil {
t.Fail()
}
}
func TestParseOciSpecFileCase2(t *testing.T) {
file := "file"
f, err := os.Create(file)
defer os.Remove(file)
defer f.Close()
if err != nil {
t.Log("create file failed")
}
err = f.Chmod(fileMode0600)
if err != nil {
t.Logf("chmod file error: %v", err)
}
_, err = parseOciSpecFile(file)
if err == nil {
t.Fail()
}
}
func TestParseOciSpecFileCase3(t *testing.T) {
file, err := os.Create(configFile)
if err != nil {
t.Log("create file failed")
t.FailNow()
}
defer os.Remove(configFile)
defer file.Close()
err = file.Chmod(fileMode0600)
if err != nil {
t.Log("chmod file failed")
t.FailNow()
}
testSpec := specs.Spec{}
jsonData, err := json.MarshalIndent(testSpec, "", " ")
if err != nil {
t.Logf("failed to MarshalIndent, err: %v", err)
t.FailNow()
}
_, err = file.Write(jsonData)
if err != nil {
t.Logf("failed to Write, err: %v", err)
t.FailNow()
}
_, err = parseOciSpecFile(configFile)
assert.Equal(t, fmt.Errorf("invalid OCI spec for empty process"), err)
}
func TestGetContainerConfig(t *testing.T) {
cmd := exec.Command("runc", "spec")
if err := cmd.Run(); err != nil {
t.Log("runc spec failed")
}
defer func() {
if err := recover(); err != nil {
t.Log("exception", err)
}
}()
defer os.Remove(configFile)
stateFile, err := os.Open(configFile)
if err != nil {
t.Log("open file failed")
}
defer stateFile.Close()
patch := gomonkey.ApplyGlobalVar(&containerConfigInputStream, stateFile)
defer patch.Reset()
getContainerConfig()
}
func TestGetContainerConfigPatch1(t *testing.T) {
convey.Convey("test getContainerConfig patch1", t, func() {
patches := gomonkey.ApplyMethodReturn(json.NewDecoder(containerConfigInputStream),
"Decode", nil)
defer patches.Reset()
convey.Convey("01-RealFileChecker error, should return error", func() {
patch := gomonkey.ApplyFuncReturn(mindxcheckutils.RealFileChecker, testStr, testError)
defer patch.Reset()
_, err := getContainerConfig()
convey.So(err, convey.ShouldBeError)
})
patches.ApplyFuncReturn(mindxcheckutils.RealFileChecker, testStr, nil)
convey.Convey("02-parseOciSpecFile error, should return error", func() {
patch := gomonkey.ApplyFuncReturn(parseOciSpecFile, &specs.Spec{}, testError)
defer patch.Reset()
_, err := getContainerConfig()
convey.So(err, convey.ShouldBeError)
})
convey.Convey("03-over MaxCommandLength, should return error", func() {
sp := &specs.Spec{
Process: &specs.Process{
Env: make([]string, MaxCommandLength+1),
},
}
patch := gomonkey.ApplyFuncReturn(parseOciSpecFile, sp, nil)
defer patch.Reset()
_, err := getContainerConfig()
convey.So(err, convey.ShouldBeError)
})
})
}
func TestGetContainerConfigPatch2(t *testing.T) {
convey.Convey("test getContainerConfig patch2", t, func() {
testSp := &specs.Spec{
Process: &specs.Process{
Env: make([]string, 1),
},
Root: &specs.Root{
Path: testStr,
},
}
patches := gomonkey.ApplyFuncReturn(mindxcheckutils.RealFileChecker, testStr, nil).
ApplyFuncReturn(parseOciSpecFile, testSp, nil).
ApplyMethodReturn(json.NewDecoder(containerConfigInputStream),
"Decode", nil)
defer patches.Reset()
convey.Convey("04-rfs not abs path, success, should return nil", func() {
patch := gomonkey.ApplyFuncReturn(filepath.Abs, testStr, false)
defer patch.Reset()
_, err := getContainerConfig()
convey.So(err, convey.ShouldBeNil)
})
})
}
type fileInfoMock struct {
os.FileInfo
}
func (f fileInfoMock) Mode() os.FileMode {
return os.ModePerm
}
func TestReadConfigsOfDir(t *testing.T) {
patch := gomonkey.ApplyFunc(os.Stat, func(name string) (os.FileInfo, error) {
return &fileInfoMock{}, nil
})
defer patch.Reset()
patchSize := gomonkey.ApplyMethod(reflect.TypeOf(&fileInfoMock{}), "Mode", func(f *fileInfoMock) fs.FileMode {
return fs.ModeDir
})
defer patchSize.Reset()
tests := []struct {
name string
dir string
configs []string
want []string
want1 []string
wantErr bool
}{
{
name: "readConfigsOfDir success case 1",
want: []string{},
want1: []string{},
},
{
name: "readConfigsOfDir fail case 2",
configs: []string{"base"},
wantErr: true,
want: nil,
want1: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1, err := readConfigsOfDir(tt.dir, tt.configs)
if (err == nil) == tt.wantErr {
t.Errorf("readConfigsOfDir() got = %v, want %v", err, tt.wantErr)
}
assert.Equalf(t, tt.want, got, "readConfigsOfDir(%v, %v)", tt.dir, tt.configs)
assert.Equalf(t, tt.want1, got1, "readConfigsOfDir(%v, %v)", tt.dir, tt.configs)
})
}
}
func TestDoPrestartHook(t *testing.T) {
tests := []struct {
name string
wantErr bool
}{
{
name: "DoPrestartHook success case 1",
wantErr: true,
},
}
conCfg := containerConfig{
Pid: pidSample,
Rootfs: ".",
Env: []string{api.AscendVisibleDevicesEnv + "=0-3,5,7", api.AscendRuntimeMountsEnv + "=a"},
}
patchCfg := gomonkey.ApplyFuncReturn(getContainerConfig, &conCfg, nil)
defer patchCfg.Reset()
patch := gomonkey.ApplyFunc(os.Stat, func(name string) (os.FileInfo, error) {
return &fileInfoMock{}, nil
})
defer patch.Reset()
patchReadConfigsOfDir := gomonkey.ApplyFunc(readConfigsOfDir,
func(dir string, configs []string) ([]string, []string, error) {
return []string{}, []string{}, nil
})
defer patchReadConfigsOfDir.Reset()
patchSize := gomonkey.ApplyMethod(reflect.TypeOf(&fileInfoMock{}), "Mode", func(f *fileInfoMock) fs.FileMode {
return fs.ModeDir
})
defer patchSize.Reset()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := DoPrestartHook()
if (err == nil) == tt.wantErr {
t.Errorf("DoPrestartHook() got = %v, want %v", err, tt.wantErr)
}
})
}
}
func TestParseRuntimeOptions(t *testing.T) {
virtualValue := "VIRTUAL"
tests := []struct {
name string
runtimeOptions string
want []string
wantErr bool
}{
{
name: "too long case 1",
runtimeOptions: strings.Repeat("a", strRepeatTimes),
wantErr: true,
},
{
name: "invalid case 2",
runtimeOptions: "a",
wantErr: true,
},
{
name: "success case 3",
runtimeOptions: virtualValue,
wantErr: false,
want: []string{virtualValue},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseRuntimeOptions(tt.runtimeOptions)
if (err == nil) == tt.wantErr {
t.Errorf("DoPrestartHook() got = %v, want %v", err, tt.wantErr)
}
assert.Equalf(t, tt.want, got, "parseRuntimeOptions(%v)", tt.runtimeOptions)
})
}
}
func TestAddUBMount(t *testing.T) {
convey.Convey("test addUBMount", t, func() {
convey.Convey("test addUBMount should do nothing when mount file and directory not exist", func() {
var fileMountList []string
var dirMountList []string
mockStat := gomonkey.ApplyFunc(os.Stat, func(name string) (os.FileInfo, error) {
return nil, os.ErrNotExist
})
defer mockStat.Reset()
fileMountList, dirMountList = addUBMount(fileMountList, dirMountList)
convey.So(fileMountList, convey.ShouldBeEmpty)
convey.So(dirMountList, convey.ShouldBeEmpty)
})
convey.Convey("test addUBMount should append list when mount file and directory exist", func() {
var fileMountList []string
var dirMountList []string
mockStat := mockStat()
defer mockStat.Reset()
fileMountList, dirMountList = addUBMount(fileMountList, dirMountList)
convey.So(fileMountList, convey.ShouldNotBeEmpty)
convey.So(dirMountList, convey.ShouldNotBeEmpty)
})
})
}
func TestGetArgs(t *testing.T) {
tests := []struct {
name string
cliPath string
containerConfig *containerConfig
fileMountList []string
dirMountList []string
allowLink string
want []string
}{
{
name: "success case 1",
containerConfig: &containerConfig{},
fileMountList: []string{"test"},
dirMountList: []string{"test"},
allowLink: "true",
cliPath: "testcli",
want: []string{"testcli", "--allow-link", "true", "--pid", "0", "--rootfs", "",
"--mount-file", "test", "--mount-dir", "test"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, getArgs(tt.cliPath, tt.containerConfig, tt.fileMountList,
tt.dirMountList, tt.allowLink), "getArgs(%v, %v, %v, %v, %v)",
tt.cliPath, tt.containerConfig, tt.fileMountList, tt.dirMountList, tt.allowLink)
})
}
}
func TestParseMounts(t *testing.T) {
tests := []struct {
name string
mounts string
want []string
}{
{
name: "base case 1",
mounts: "",
want: []string{baseConfig},
},
{
name: "base case 2",
mounts: strings.Repeat("a", strRepeatTimes),
want: []string{baseConfig},
},
{
name: "other case 3",
mounts: "testList,testList1",
want: []string{"testlist", "testlist1"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, parseMounts(tt.mounts), "parseMounts(%v)", tt.mounts)
})
}
}
func TestReadMountConfig(t *testing.T) {
convey.Convey("test readMountConfig", t, func() {
convey.Convey("01-get abs path failed, should return error", func() {
patch := gomonkey.ApplyFuncReturn(filepath.Abs, "", testError)
defer patch.Reset()
_, _, err := readMountConfig("", "")
convey.So(err, convey.ShouldBeError)
})
patches := gomonkey.ApplyFuncReturn(filepath.Abs, "", nil)
defer patches.Reset()
convey.Convey("02-stat error, should return error", func() {
patch := gomonkey.ApplyFuncReturn(os.Stat, fileInfoMock{}, testError)
defer patch.Reset()
_, _, err := readMountConfig("", "")
convey.So(err, convey.ShouldBeError)
})
patches.ApplyFuncReturn(os.Stat, fileInfoMock{}, nil).
ApplyFuncReturn(mindxcheckutils.RealFileChecker, "", nil)
convey.Convey("03-file info is not regular, should return error", func() {
var fm os.FileMode
patch := gomonkey.ApplyMethodReturn(fm, "IsRegular", false)
defer patch.Reset()
_, _, err := readMountConfig("", "")
convey.So(err, convey.ShouldBeError)
})
var fm os.FileMode
patches.ApplyMethodReturn(fm, "IsRegular", true)
convey.Convey("04-open file error, should return error", func() {
patch := gomonkey.ApplyFuncReturn(os.Open, &os.File{}, testError)
defer patch.Reset()
_, _, err := readMountConfig("", "")
convey.So(err, convey.ShouldBeError)
})
patches.ApplyFuncReturn(os.Open, &os.File{}, nil)
convey.Convey("05-success, should return nil", func() {
scanNum := 0
patch := gomonkey.ApplyMethod(&bufio.Scanner{}, "Scan",
func(scanner *bufio.Scanner) bool {
scanNum++
if scanNum > 1 {
return false
} else {
return true
}
})
defer patch.Reset()
_, _, err := readMountConfig("", "")
convey.So(err, convey.ShouldBeNil)
})
})
}
type mockFileInfo struct {
mode os.FileMode
sys interface{}
}
func (m mockFileInfo) Name() string { return "mock" }
func (m mockFileInfo) Size() int64 { return 0 }
func (m mockFileInfo) Mode() os.FileMode { return m.mode }
func (m mockFileInfo) ModTime() time.Time { return time.Now() }
func (m mockFileInfo) IsDir() bool { return false }
func (m mockFileInfo) Sys() interface{} { return m.sys }
func mockStat() *gomonkey.Patches {
return gomonkey.ApplyFunc(os.Stat, func(name string) (os.FileInfo, error) {
mockMountItemMap := map[string]mockFileInfo{
hcclRootInfo: {mode: os.ModeTemporary},
topoDirPath: {mode: os.ModeDir},
}
return mockMountItemMap[name], nil
})
}