Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions tests/e2e/json_test_cases.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright (c) 2023-2026, Nubificus LTD
//
// 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 urunce2etesting

import (
"encoding/json"
"fmt"
"os"
)

// jsonTestCase is the JSON-serializable form of containerTestArgs.
// TestFunc is omitted — JSON cases always have a nil TestFunc, which
// routes them to runForegroundTest (output matching via ExpectOut).
type jsonTestCase struct {
Image string `json:"image"`
Name string `json:"name"`
Devmapper bool `json:"devmapper"`
Seccomp bool `json:"seccomp"`
UID int `json:"uid"`
GID int `json:"gid"`
Groups []int64 `json:"groups"`
Memory string `json:"memory"`
Cli string `json:"cli"`
Volumes []containerVolume `json:"volumes"`
StaticNet bool `json:"staticNet"`
SideContainers []string `json:"sideContainers"`
Skippable bool `json:"skippable"`
ExpectOut string `json:"expectOut"`
}

func (j jsonTestCase) toContainerTestArgs() containerTestArgs {
groups := j.Groups
if groups == nil {
groups = []int64{}
}
volumes := j.Volumes
if volumes == nil {
volumes = []containerVolume{}
}
sideContainers := j.SideContainers
if sideContainers == nil {
sideContainers = []string{}
}
return containerTestArgs{
Image: j.Image,
Name: j.Name,
Devmapper: j.Devmapper,
Seccomp: j.Seccomp,
UID: j.UID,
GID: j.GID,
Groups: groups,
Memory: j.Memory,
Cli: j.Cli,
Volumes: volumes,
StaticNet: j.StaticNet,
SideContainers: sideContainers,
Skippable: j.Skippable,
ExpectOut: j.ExpectOut,
}
}

// loadTestCasesFromJSON reads a JSON array of test cases from path.
func loadTestCasesFromJSON(path string) ([]containerTestArgs, error) {
data, err := os.ReadFile(path) //nolint:gosec
if err != nil {
return nil, fmt.Errorf("read %s: %w", path, err)
}

var cases []jsonTestCase
if err := json.Unmarshal(data, &cases); err != nil {
return nil, fmt.Errorf("unmarshal %s: %w", path, err)
}

result := make([]containerTestArgs, len(cases))
for i, c := range cases {
result[i] = c.toContainerTestArgs()
}
return result, nil
}

// optionalJSONTestCases returns test cases from path, or an empty slice if
// the file does not exist. Parse errors are reported to stderr.
func optionalJSONTestCases(path string) []containerTestArgs {
if _, err := os.Stat(path); os.IsNotExist(err) {
return []containerTestArgs{}
}
cases, err := loadTestCasesFromJSON(path)
if err != nil {
fmt.Fprintf(os.Stderr, "warning: failed to load JSON test cases from %s: %v\n", path, err) //nolint:errcheck
return []containerTestArgs{}
}
return cases
}
162 changes: 162 additions & 0 deletions tests/e2e/json_test_cases_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// Copyright (c) 2023-2026, Nubificus LTD
//
// 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 urunce2etesting

import (
"encoding/json"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestLoadTestCasesFromJSON(t *testing.T) {
tests := []struct {
name string
content string
wantErr bool
wantLen int
checkCase func(t *testing.T, got []containerTestArgs)
}{
{
name: "valid single case",
content: `[{"image":"img:latest","name":"tc","expectOut":"hello","seccomp":true}]`,
wantLen: 1,
checkCase: func(t *testing.T, got []containerTestArgs) {
assert.Equal(t, "img:latest", got[0].Image)
assert.Equal(t, "tc", got[0].Name)
assert.Equal(t, "hello", got[0].ExpectOut)
assert.True(t, got[0].Seccomp)
assert.Nil(t, got[0].TestFunc)
},
},
{
name: "empty array returns empty slice",
content: `[]`,
wantLen: 0,
},
{
name: "nil slices become empty slices",
content: `[{"image":"i","name":"n","expectOut":""}]`,
wantLen: 1,
checkCase: func(t *testing.T, got []containerTestArgs) {
assert.Equal(t, []int64{}, got[0].Groups)
assert.Equal(t, []containerVolume{}, got[0].Volumes)
assert.Equal(t, []string{}, got[0].SideContainers)
},
},
{
name: "volumes are decoded correctly",
content: func() string {
cases := []jsonTestCase{{
Image: "i",
Name: "n",
ExpectOut: "",
Volumes: []containerVolume{{Source: "/src", Dest: "/dst"}},
}}
b, _ := json.Marshal(cases)
return string(b)
}(),
wantLen: 1,
checkCase: func(t *testing.T, got []containerTestArgs) {
require.Len(t, got[0].Volumes, 1)
assert.Equal(t, "/src", got[0].Volumes[0].Source)
assert.Equal(t, "/dst", got[0].Volumes[0].Dest)
},
},
{
name: "file not found returns error",
content: "", // signal to use a non-existent path
wantErr: true,
},
{
name: "invalid JSON returns error",
content: `{not valid json`,
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

var path string
if tt.wantErr && tt.content == "" {
path = filepath.Join(t.TempDir(), "nonexistent.json")
} else {
path = filepath.Join(t.TempDir(), "cases.json")
require.NoError(t, os.WriteFile(path, []byte(tt.content), 0o600))
}

got, err := loadTestCasesFromJSON(path)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.Len(t, got, tt.wantLen)
if tt.checkCase != nil {
tt.checkCase(t, got)
}
})
}
}

func TestOptionalJSONTestCases(t *testing.T) {
tests := []struct {
name string
setup func(t *testing.T) string
wantLen int
}{
{
name: "absent file returns empty slice",
setup: func(t *testing.T) string {
return filepath.Join(t.TempDir(), "missing.json")
},
wantLen: 0,
},
{
name: "valid file returns cases",
setup: func(t *testing.T) string {
cases := []jsonTestCase{{Image: "img:latest", Name: "tc", ExpectOut: "ok"}}
data, _ := json.Marshal(cases)
path := filepath.Join(t.TempDir(), "cases.json")
require.NoError(t, os.WriteFile(path, data, 0o600))
return path
},
wantLen: 1,
},
{
name: "invalid JSON returns empty slice",
setup: func(t *testing.T) string {
path := filepath.Join(t.TempDir(), "cases.json")
require.NoError(t, os.WriteFile(path, []byte("{bad json"), 0o600))
return path
},
wantLen: 0,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
path := tt.setup(t)
got := optionalJSONTestCases(path)
assert.Len(t, got, tt.wantLen)
})
}
}
12 changes: 8 additions & 4 deletions tests/e2e/test_cases.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
package urunce2etesting

func nerdctlTestCases() []containerTestArgs {
return []containerTestArgs{
cases := []containerTestArgs{
{
Image: "harbor.nbfc.io/nubificus/urunc/hello-hvt-rumprun:latest",
Name: "Hvt-rumprun-capture-hello",
Expand Down Expand Up @@ -457,10 +457,11 @@ func nerdctlTestCases() []containerTestArgs {
TestFunc: pingTest,
},
}
return append(cases, optionalJSONTestCases("testdata/nerdctl_extra_test_cases.json")...)
}

func ctrTestCases() []containerTestArgs {
return []containerTestArgs{
cases := []containerTestArgs{
{
Image: "harbor.nbfc.io/nubificus/urunc/hello-hvt-rumprun-nonet:latest",
Name: "Hvt-rumprun-hello",
Expand Down Expand Up @@ -751,10 +752,11 @@ func ctrTestCases() []containerTestArgs {
TestFunc: matchTest,
},
}
return append(cases, optionalJSONTestCases("testdata/ctr_extra_test_cases.json")...)
}

func crictlTestCases() []containerTestArgs {
return []containerTestArgs{
cases := []containerTestArgs{
{
Image: "harbor.nbfc.io/nubificus/urunc/redis-hvt-rumprun-raw:latest",
Name: "Hvt-rumprun-redis",
Expand Down Expand Up @@ -964,10 +966,11 @@ func crictlTestCases() []containerTestArgs {
TestFunc: userGroupTest,
},
}
return cases
}

func dockerTestCases() []containerTestArgs {
return []containerTestArgs{
cases := []containerTestArgs{
{
Image: "harbor.nbfc.io/nubificus/urunc/net-spt-mirage:latest",
Name: "Spt-mirage-net",
Expand Down Expand Up @@ -1209,4 +1212,5 @@ func dockerTestCases() []containerTestArgs {
TestFunc: namespaceTest,
},
}
return cases
}
1 change: 1 addition & 0 deletions tests/e2e/testdata/ctr_extra_test_cases.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
1 change: 1 addition & 0 deletions tests/e2e/testdata/nerdctl_extra_test_cases.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]