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 grus
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"github.com/agiledragon/gomonkey/v2"
"github.com/opencontainers/runtime-spec/specs-go"
"ascend-docker-runtime/runtime/common"
"ascend-docker-runtime/runtime/grus/runtime"
)
func TestReadConfigJson(t *testing.T) {
tests := []struct {
name string
prepareFile func(path string) error
expectedErr bool
}{
{
name: "file not found",
prepareFile: func(path string) error {
return nil
},
expectedErr: true,
},
{
name: "invalid JSON",
prepareFile: func(path string) error {
return os.WriteFile(path, []byte("invalid json"), 0644)
},
expectedErr: true,
},
{
name: "valid JSON",
prepareFile: func(path string) error {
config := specs.Spec{
Version: "1.0.2",
}
data, err := json.Marshal(config)
if err != nil {
return err
}
return os.WriteFile(path, data, 0644)
},
expectedErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tempDir, err := os.MkdirTemp("", "read-config-test")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tempDir)
configPath := filepath.Join(tempDir, "config.json")
if err := tt.prepareFile(configPath); err != nil {
t.Fatalf("Failed to prepare file: %v", err)
}
_, err = readConfigJson(configPath)
if (err != nil) != tt.expectedErr {
t.Fatalf("Expected error: %v, got: %v", tt.expectedErr, err != nil)
}
})
}
}
func TestReadOCIConfig(t *testing.T) {
tests := []struct {
name string
bundlePath string
prepareFile func(bundlePath string) error
expectedErr bool
}{
{
name: "bundle path is empty and getwd fails",
bundlePath: "",
prepareFile: func(bundlePath string) error {
return nil
},
expectedErr: true,
},
{
name: "bundle path exists but config.json not found",
bundlePath: "test-bundle",
prepareFile: func(bundlePath string) error {
return os.MkdirAll(bundlePath, 0755)
},
expectedErr: true,
},
{
name: "bundle path exists with valid config.json",
bundlePath: "test-bundle",
prepareFile: func(bundlePath string) error {
if err := os.MkdirAll(bundlePath, 0755); err != nil {
return err
}
config := specs.Spec{
Version: "1.0.2",
}
data, err := json.Marshal(config)
if err != nil {
return err
}
return os.WriteFile(filepath.Join(bundlePath, "config.json"), data, 0644)
},
expectedErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tempDir, err := os.MkdirTemp("", "read-oci-config-test")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tempDir)
bundlePath := tt.bundlePath
if bundlePath != "" {
bundlePath = filepath.Join(tempDir, bundlePath)
}
if err := tt.prepareFile(bundlePath); err != nil {
t.Fatalf("Failed to prepare file: %v", err)
}
_, err = readOCIConfig(bundlePath)
if (err != nil) != tt.expectedErr {
t.Fatalf("Expected error: %v, got: %v", tt.expectedErr, err != nil)
}
})
}
}
func TestAddEnv(t *testing.T) {
tests := []struct {
name string
spec *specs.Spec
key string
env string
expectedEnvs []string
}{
{
name: "spec is nil",
spec: nil,
key: "TEST_KEY",
env: "TEST_KEY=test_value",
expectedEnvs: nil,
},
{
name: "spec.Process is nil",
spec: &specs.Spec{},
key: "TEST_KEY",
env: "TEST_KEY=test_value",
expectedEnvs: nil,
},
{
name: "spec.Process.Env is nil",
spec: &specs.Spec{
Process: &specs.Process{},
},
key: "TEST_KEY",
env: "TEST_KEY=test_value",
expectedEnvs: nil,
},
{
name: "add new env",
spec: &specs.Spec{
Process: &specs.Process{
Env: []string{"EXISTING_KEY=existing_value"},
},
},
key: "NEW_KEY",
env: "NEW_KEY=new_value",
expectedEnvs: []string{"EXISTING_KEY=existing_value", "NEW_KEY=new_value"},
},
{
name: "update existing env",
spec: &specs.Spec{
Process: &specs.Process{
Env: []string{"EXISTING_KEY=old_value", "OTHER_KEY=other_value"},
},
},
key: "EXISTING_KEY",
env: "EXISTING_KEY=new_value",
expectedEnvs: []string{"EXISTING_KEY=new_value", "OTHER_KEY=other_value"},
},
{
name: "empty env list",
spec: &specs.Spec{
Process: &specs.Process{
Env: []string{},
},
},
key: "NEW_KEY",
env: "NEW_KEY=new_value",
expectedEnvs: []string{"NEW_KEY=new_value"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
addEnv(tt.spec, tt.key, tt.env)
if tt.spec == nil || tt.spec.Process == nil || tt.spec.Process.Env == nil {
return
}
if len(tt.spec.Process.Env) != len(tt.expectedEnvs) {
t.Fatalf("Expected %d env vars, got %d", len(tt.expectedEnvs), len(tt.spec.Process.Env))
}
for i, expectedEnv := range tt.expectedEnvs {
if tt.spec.Process.Env[i] != expectedEnv {
t.Fatalf("Expected env[%d]=%s, got %s", i, expectedEnv, tt.spec.Process.Env[i])
}
}
})
}
}
func TestSstart(t *testing.T) {
tests := []struct {
name string
args *common.Args
mockRuntimeState bool
mockExecRunc bool
mockReadOCIConfig bool
readOCIConfigErr bool
hasSnapshotEnv bool
expectedErr bool
}{
{
name: "bundle is empty and state fails",
args: &common.Args{
ContainerID: "test-container",
Root: "/test/root",
Bundle: "",
},
mockRuntimeState: true,
mockExecRunc: true,
expectedErr: false,
},
{
name: "readOCIConfig fails",
args: &common.Args{
ContainerID: "test-container",
Root: "/test/root",
Bundle: "/test/bundle",
},
mockReadOCIConfig: true,
readOCIConfigErr: true,
mockExecRunc: true,
expectedErr: false,
},
{
name: "has snapshot image path env",
args: &common.Args{
ContainerID: "test-container",
Root: "/test/root",
Bundle: "/test/bundle",
},
mockReadOCIConfig: true,
hasSnapshotEnv: true,
expectedErr: false,
},
{
name: "no snapshot image path env",
args: &common.Args{
ContainerID: "test-container",
Root: "/test/root",
Bundle: "/test/bundle",
},
mockReadOCIConfig: true,
hasSnapshotEnv: false,
mockExecRunc: true,
expectedErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
if tt.mockRuntimeState {
patches.ApplyMethodFunc(&runtime.RuncRuntime{}, "State", func(id string) (*runtime.StateInfo, error) {
return &runtime.StateInfo{
Bundle: "/test/bundle",
}, nil
})
}
if tt.mockExecRunc {
patches.ApplyFunc(common.ExecRunc, func() error {
return nil
})
}
if tt.mockReadOCIConfig {
var spec *specs.Spec
var err error
if tt.readOCIConfigErr {
err = os.ErrNotExist
} else {
spec = &specs.Spec{}
if tt.hasSnapshotEnv {
spec.Process = &specs.Process{
Env: []string{
common.GRUS_SNAPSHOT_IMAGE_PATH + "=/test/image/path",
common.POD_NAME + "=" + "test-0",
},
}
if err := os.MkdirAll("/test/image/path/0", 0755); err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
err = os.WriteFile("/test/image/path/0/tempfile", []byte("content"), 0660)
if err != nil {
t.Fatalf("Failed to create temp file: %v", err)
}
} else {
spec.Process = &specs.Process{
Env: []string{""},
}
}
}
patches.ApplyFunc(readOCIConfig, func(bundlePath string) (*specs.Spec, error) {
return spec, err
})
}
err := Sstart(tt.args)
if (err != nil) != tt.expectedErr {
t.Fatalf("Expected error: %v, got: %v", tt.expectedErr, err != nil)
}
})
}
}