Skip to content

Commit 8e7b2bf

Browse files
refactor: migrate multi_platform_linux_test.go to nerdtest.Setup
Replace testutil.NewBase with nerdtest.Setup throughout multi_platform_linux_test.go, following the Tigron framework pattern (#4641). Changes: - Use nerdtest.RegistryWithNoAuth instead of testregistry.NewWithNoAuth - Use data.Temp().Save() for Dockerfiles and compose files - Convert testMultiPlatformRun helper to use test.Helpers - Use SubTests in TestMultiPlatformRun for per-platform isolation - Register builder cache cleanup in Cleanup callbacks - Add requireMultiPlatformExec requirement using platformutil Signed-off-by: Ogulcan Aydogan <ogulcanaydogan@hotmail.com>
1 parent ad4795a commit 8e7b2bf

1 file changed

Lines changed: 203 additions & 96 deletions

File tree

cmd/nerdctl/container/multi_platform_linux_test.go

Lines changed: 203 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,35 @@ import (
2222
"strings"
2323
"testing"
2424

25-
"gotest.tools/v3/assert"
25+
"github.com/containerd/nerdctl/mod/tigron/expect"
26+
"github.com/containerd/nerdctl/mod/tigron/require"
27+
"github.com/containerd/nerdctl/mod/tigron/test"
2628

27-
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
29+
"github.com/containerd/nerdctl/v2/pkg/platformutil"
2830
"github.com/containerd/nerdctl/v2/pkg/testutil"
31+
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
2932
"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil"
30-
"github.com/containerd/nerdctl/v2/pkg/testutil/testregistry"
3133
)
3234

33-
func testMultiPlatformRun(base *testutil.Base, alpineImage string) {
34-
t := base.T
35-
testutil.RequireExecPlatform(t, "linux/amd64", "linux/arm64", "linux/arm/v7")
35+
// requireMultiPlatformExec skips the test when the host cannot execute
36+
// linux/amd64, linux/arm64 and linux/arm/v7 images (e.g. no binfmt_misc).
37+
var requireMultiPlatformExec = &test.Requirement{
38+
Check: func(_ test.Data, _ test.Helpers) (bool, string) {
39+
ok, err := platformutil.CanExecProbably("linux/amd64", "linux/arm64", "linux/arm/v7")
40+
if !ok {
41+
msg := "requires multi-platform exec support (linux/amd64, linux/arm64, linux/arm/v7)"
42+
if err != nil {
43+
msg += ": " + err.Error()
44+
}
45+
return false, msg
46+
}
47+
return true, ""
48+
},
49+
}
50+
51+
// assertMultiPlatformRun runs uname -m inside image on each platform and
52+
// asserts the expected machine type string.
53+
func assertMultiPlatformRun(helpers test.Helpers, image string) {
3654
testCases := map[string]string{
3755
"amd64": "x86_64",
3856
"arm64": "aarch64",
@@ -41,92 +59,165 @@ func testMultiPlatformRun(base *testutil.Base, alpineImage string) {
4159
"linux/arm/v7": "armv7l",
4260
}
4361
for plat, expectedUnameM := range testCases {
44-
t.Logf("Testing %q (%q)", plat, expectedUnameM)
45-
cmd := base.Cmd("run", "--rm", "--platform="+plat, alpineImage, "uname", "-m")
46-
cmd.AssertOutExactly(expectedUnameM + "\n")
62+
helpers.T().Logf("Testing platform %q (%q)", plat, expectedUnameM)
63+
helpers.Command("run", "--rm", "--platform="+plat, image, "uname", "-m").
64+
Run(&test.Expected{
65+
Output: expect.Equals(expectedUnameM + "\n"),
66+
})
4767
}
4868
}
4969

5070
func TestMultiPlatformRun(t *testing.T) {
51-
base := testutil.NewBase(t)
52-
testMultiPlatformRun(base, testutil.AlpineImage)
71+
testCase := nerdtest.Setup()
72+
73+
testCase.Require = requireMultiPlatformExec
74+
75+
testCasePlatforms := map[string]string{
76+
"amd64": "x86_64",
77+
"arm64": "aarch64",
78+
"arm": "armv7l",
79+
"linux/arm": "armv7l",
80+
"linux/arm/v7": "armv7l",
81+
}
82+
for plat, expectedUnameM := range testCasePlatforms {
83+
p, e := plat, expectedUnameM
84+
testCase.SubTests = append(testCase.SubTests, &test.Case{
85+
Description: p,
86+
Command: test.Command("run", "--rm", "--platform="+p, testutil.AlpineImage, "uname", "-m"),
87+
Expected: test.Expects(0, nil, expect.Equals(e+"\n")),
88+
})
89+
}
90+
91+
testCase.Run(t)
5392
}
5493

5594
func TestMultiPlatformBuildPush(t *testing.T) {
56-
testutil.DockerIncompatible(t) // non-buildx version of `docker build` lacks multi-platform. Also, `docker push` lacks --platform.
57-
testutil.RequiresBuild(t)
58-
testutil.RegisterBuildCacheCleanup(t)
59-
testutil.RequireExecPlatform(t, "linux/amd64", "linux/arm64", "linux/arm/v7")
60-
base := testutil.NewBase(t)
61-
tID := testutil.Identifier(t)
62-
reg := testregistry.NewWithNoAuth(base, 0, false)
63-
defer reg.Cleanup(nil)
64-
65-
imageName := fmt.Sprintf("localhost:%d/%s:latest", reg.Port, tID)
66-
defer base.Cmd("rmi", imageName).Run()
67-
68-
dockerfile := fmt.Sprintf(`FROM %s
69-
RUN echo dummy
70-
`, testutil.AlpineImage)
71-
72-
buildCtx := helpers.CreateBuildContext(t, dockerfile)
73-
74-
base.Cmd("build", "-t", imageName, "--platform=amd64,arm64,linux/arm/v7", buildCtx).AssertOK()
75-
testMultiPlatformRun(base, imageName)
76-
base.Cmd("push", "--platform=amd64,arm64,linux/arm/v7", imageName).AssertOK()
95+
testCase := nerdtest.Setup()
96+
97+
testCase.Require = require.All(
98+
require.Not(nerdtest.Docker),
99+
nerdtest.Build,
100+
requireMultiPlatformExec,
101+
nerdtest.Registry,
102+
)
103+
104+
testCase.Setup = func(data test.Data, helpers test.Helpers) {
105+
reg := nerdtest.RegistryWithNoAuth(data, helpers, 0, false)
106+
imageName := fmt.Sprintf("localhost:%d/%s:latest", reg.Port, data.Identifier())
107+
data.Labels().Set("image", imageName)
108+
109+
dockerfile := fmt.Sprintf("FROM %s\nRUN echo dummy\n", testutil.AlpineImage)
110+
buildCtx := data.Temp().Dir()
111+
data.Temp().Save(dockerfile, "Dockerfile")
112+
113+
helpers.Ensure("build", "-t", imageName, "--platform=amd64,arm64,linux/arm/v7", buildCtx)
114+
}
115+
116+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
117+
if img := data.Labels().Get("image"); img != "" {
118+
helpers.Anyhow("rmi", img)
119+
}
120+
helpers.Anyhow("builder", "prune", "--all", "--force")
121+
}
122+
123+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
124+
imageName := data.Labels().Get("image")
125+
assertMultiPlatformRun(helpers, imageName)
126+
return helpers.Command("push", "--platform=amd64,arm64,linux/arm/v7", imageName)
127+
}
128+
129+
testCase.Expected = test.Expects(0, nil, nil)
130+
131+
testCase.Run(t)
77132
}
78133

79-
// TestMultiPlatformBuildPushNoRun tests if the push succeeds in a situation where nerdctl builds
80-
// a Dockerfile without RUN, COPY, etc commands. In such situation, BuildKit doesn't download the base image
81-
// so nerdctl needs to ensure these blobs to be locally available.
82134
func TestMultiPlatformBuildPushNoRun(t *testing.T) {
83-
testutil.DockerIncompatible(t) // non-buildx version of `docker build` lacks multi-platform. Also, `docker push` lacks --platform.
84-
testutil.RequiresBuild(t)
85-
testutil.RegisterBuildCacheCleanup(t)
86-
testutil.RequireExecPlatform(t, "linux/amd64", "linux/arm64", "linux/arm/v7")
87-
base := testutil.NewBase(t)
88-
tID := testutil.Identifier(t)
89-
reg := testregistry.NewWithNoAuth(base, 0, false)
90-
defer reg.Cleanup(nil)
91-
92-
imageName := fmt.Sprintf("localhost:%d/%s:latest", reg.Port, tID)
93-
defer base.Cmd("rmi", imageName).Run()
94-
95-
dockerfile := fmt.Sprintf(`FROM %s
96-
CMD echo dummy
97-
`, testutil.AlpineImage)
98-
99-
buildCtx := helpers.CreateBuildContext(t, dockerfile)
100-
101-
base.Cmd("build", "-t", imageName, "--platform=amd64,arm64,linux/arm/v7", buildCtx).AssertOK()
102-
testMultiPlatformRun(base, imageName)
103-
base.Cmd("push", "--platform=amd64,arm64,linux/arm/v7", imageName).AssertOK()
135+
testCase := nerdtest.Setup()
136+
137+
testCase.Require = require.All(
138+
require.Not(nerdtest.Docker),
139+
nerdtest.Build,
140+
requireMultiPlatformExec,
141+
nerdtest.Registry,
142+
)
143+
144+
testCase.Setup = func(data test.Data, helpers test.Helpers) {
145+
reg := nerdtest.RegistryWithNoAuth(data, helpers, 0, false)
146+
imageName := fmt.Sprintf("localhost:%d/%s:latest", reg.Port, data.Identifier())
147+
data.Labels().Set("image", imageName)
148+
149+
dockerfile := fmt.Sprintf("FROM %s\nCMD echo dummy\n", testutil.AlpineImage)
150+
buildCtx := data.Temp().Dir()
151+
data.Temp().Save(dockerfile, "Dockerfile")
152+
153+
helpers.Ensure("build", "-t", imageName, "--platform=amd64,arm64,linux/arm/v7", buildCtx)
154+
}
155+
156+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
157+
if img := data.Labels().Get("image"); img != "" {
158+
helpers.Anyhow("rmi", img)
159+
}
160+
helpers.Anyhow("builder", "prune", "--all", "--force")
161+
}
162+
163+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
164+
imageName := data.Labels().Get("image")
165+
assertMultiPlatformRun(helpers, imageName)
166+
return helpers.Command("push", "--platform=amd64,arm64,linux/arm/v7", imageName)
167+
}
168+
169+
testCase.Expected = test.Expects(0, nil, nil)
170+
171+
testCase.Run(t)
104172
}
105173

106174
func TestMultiPlatformPullPushAllPlatforms(t *testing.T) {
107-
testutil.DockerIncompatible(t)
108-
base := testutil.NewBase(t)
109-
tID := testutil.Identifier(t)
110-
reg := testregistry.NewWithNoAuth(base, 0, false)
111-
defer reg.Cleanup(nil)
112-
113-
pushImageName := fmt.Sprintf("localhost:%d/%s:latest", reg.Port, tID)
114-
defer base.Cmd("rmi", pushImageName).Run()
115-
116-
base.Cmd("pull", "--quiet", "--all-platforms", testutil.AlpineImage).AssertOK()
117-
base.Cmd("tag", testutil.AlpineImage, pushImageName).AssertOK()
118-
base.Cmd("push", "--all-platforms", pushImageName).AssertOK()
119-
testMultiPlatformRun(base, pushImageName)
175+
testCase := nerdtest.Setup()
176+
177+
testCase.Require = require.All(
178+
require.Not(nerdtest.Docker),
179+
requireMultiPlatformExec,
180+
nerdtest.Registry,
181+
)
182+
183+
testCase.Setup = func(data test.Data, helpers test.Helpers) {
184+
reg := nerdtest.RegistryWithNoAuth(data, helpers, 0, false)
185+
pushImageName := fmt.Sprintf("localhost:%d/%s:latest", reg.Port, data.Identifier())
186+
data.Labels().Set("image", pushImageName)
187+
helpers.Ensure("pull", "--quiet", "--all-platforms", testutil.AlpineImage)
188+
helpers.Ensure("tag", testutil.AlpineImage, pushImageName)
189+
}
190+
191+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
192+
if img := data.Labels().Get("image"); img != "" {
193+
helpers.Anyhow("rmi", img)
194+
}
195+
}
196+
197+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
198+
pushImageName := data.Labels().Get("image")
199+
helpers.Ensure("push", "--all-platforms", pushImageName)
200+
assertMultiPlatformRun(helpers, pushImageName)
201+
return helpers.Command("inspect", "--type=image", pushImageName)
202+
}
203+
204+
testCase.Expected = test.Expects(0, nil, nil)
205+
206+
testCase.Run(t)
120207
}
121208

122209
func TestMultiPlatformComposeUpBuild(t *testing.T) {
123-
testutil.DockerIncompatible(t)
124-
testutil.RequiresBuild(t)
125-
testutil.RegisterBuildCacheCleanup(t)
126-
testutil.RequireExecPlatform(t, "linux/amd64", "linux/arm64", "linux/arm/v7")
127-
base := testutil.NewBase(t)
210+
testCase := nerdtest.Setup()
211+
212+
testCase.Require = require.All(
213+
require.Not(nerdtest.Docker),
214+
nerdtest.Build,
215+
requireMultiPlatformExec,
216+
)
128217

129-
const dockerComposeYAML = `
218+
testCase.Setup = func(data test.Data, helpers test.Helpers) {
219+
dockerfile := fmt.Sprintf("FROM %s\nRUN uname -m > /usr/share/nginx/html/index.html\n", testutil.NginxAlpineImage)
220+
composeYAML := `
130221
services:
131222
svc0:
132223
build: .
@@ -144,30 +235,46 @@ services:
144235
ports:
145236
- 8082:80
146237
`
147-
dockerfile := fmt.Sprintf(`FROM %s
148-
RUN uname -m > /usr/share/nginx/html/index.html
149-
`, testutil.NginxAlpineImage)
238+
buildCtx := data.Temp().Dir()
239+
composePath := data.Temp().Save(composeYAML, "compose.yaml")
240+
_ = buildCtx
241+
data.Temp().Save(dockerfile, "Dockerfile")
242+
data.Labels().Set("composePath", composePath)
150243

151-
comp := testutil.NewComposeDir(t, dockerComposeYAML)
152-
defer comp.CleanUp()
153-
154-
comp.WriteFile("Dockerfile", dockerfile)
155-
156-
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d", "--build").AssertOK()
157-
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run()
244+
helpers.Ensure("compose", "-f", composePath, "up", "-d", "--build")
245+
}
158246

159-
testCases := map[string]string{
160-
"http://127.0.0.1:8080": "x86_64",
161-
"http://127.0.0.1:8081": "aarch64",
162-
"http://127.0.0.1:8082": "armv7l",
247+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
248+
if cp := data.Labels().Get("composePath"); cp != "" {
249+
helpers.Anyhow("compose", "-f", cp, "down", "-v")
250+
}
251+
helpers.Anyhow("builder", "prune", "--all", "--force")
163252
}
164253

165-
for testURL, expectedIndexHTML := range testCases {
166-
resp, err := nettestutil.HTTPGet(testURL, 5, false)
167-
assert.NilError(t, err)
168-
respBody, err := io.ReadAll(resp.Body)
169-
assert.NilError(t, err)
170-
t.Logf("respBody=%q", respBody)
171-
assert.Assert(t, strings.Contains(string(respBody), expectedIndexHTML))
254+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
255+
urlExpected := map[string]string{
256+
"http://127.0.0.1:8080": "x86_64",
257+
"http://127.0.0.1:8081": "aarch64",
258+
"http://127.0.0.1:8082": "armv7l",
259+
}
260+
for url, expected := range urlExpected {
261+
resp, err := nettestutil.HTTPGet(url, 5, false)
262+
if err != nil {
263+
helpers.T().Fatalf("GET %s: %v", url, err)
264+
}
265+
body, err := io.ReadAll(resp.Body)
266+
resp.Body.Close()
267+
if err != nil {
268+
helpers.T().Fatalf("reading body from %s: %v", url, err)
269+
}
270+
if !strings.Contains(string(body), expected) {
271+
helpers.T().Errorf("expected %q in body from %s, got %q", expected, url, string(body))
272+
}
273+
}
274+
return helpers.Command("compose", "-f", data.Labels().Get("composePath"), "ps")
172275
}
276+
277+
testCase.Expected = test.Expects(0, nil, nil)
278+
279+
testCase.Run(t)
173280
}

0 commit comments

Comments
 (0)