From 3a009e64ec4531ccdb1486752295094ed852a187 Mon Sep 17 00:00:00 2001 From: Md Raiyan Date: Tue, 26 May 2026 00:10:25 +0000 Subject: [PATCH] feat(e2e): add JSON file support for extra test cases Fixes: #123 Signed-off-by: Md Raiyan --- tests/e2e/json_test_cases.go | 105 ++++++++++++ tests/e2e/json_test_cases_test.go | 162 ++++++++++++++++++ tests/e2e/test_cases.go | 12 +- tests/e2e/testdata/ctr_extra_test_cases.json | 1 + .../testdata/nerdctl_extra_test_cases.json | 1 + 5 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 tests/e2e/json_test_cases.go create mode 100644 tests/e2e/json_test_cases_test.go create mode 100644 tests/e2e/testdata/ctr_extra_test_cases.json create mode 100644 tests/e2e/testdata/nerdctl_extra_test_cases.json diff --git a/tests/e2e/json_test_cases.go b/tests/e2e/json_test_cases.go new file mode 100644 index 000000000..7e57b7863 --- /dev/null +++ b/tests/e2e/json_test_cases.go @@ -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 +} diff --git a/tests/e2e/json_test_cases_test.go b/tests/e2e/json_test_cases_test.go new file mode 100644 index 000000000..7de3cd894 --- /dev/null +++ b/tests/e2e/json_test_cases_test.go @@ -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) + }) + } +} diff --git a/tests/e2e/test_cases.go b/tests/e2e/test_cases.go index 31bec3d88..516abb9c5 100644 --- a/tests/e2e/test_cases.go +++ b/tests/e2e/test_cases.go @@ -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", @@ -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", @@ -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", @@ -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", @@ -1209,4 +1212,5 @@ func dockerTestCases() []containerTestArgs { TestFunc: namespaceTest, }, } + return cases } diff --git a/tests/e2e/testdata/ctr_extra_test_cases.json b/tests/e2e/testdata/ctr_extra_test_cases.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/tests/e2e/testdata/ctr_extra_test_cases.json @@ -0,0 +1 @@ +[] diff --git a/tests/e2e/testdata/nerdctl_extra_test_cases.json b/tests/e2e/testdata/nerdctl_extra_test_cases.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/tests/e2e/testdata/nerdctl_extra_test_cases.json @@ -0,0 +1 @@ +[]