Skip to content

Commit 8c4eaec

Browse files
authored
Merge pull request #4897 from ogulcanaydogan/refactor/nerdctl-tigron-multi-platform-test
refactor: migrate multi_platform_linux_test.go to nerdtest.Setup
2 parents 96fb06e + 83b54f0 commit 8c4eaec

1 file changed

Lines changed: 219 additions & 96 deletions

File tree

cmd/nerdctl/container/multi_platform_linux_test.go

Lines changed: 219 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,39 @@ 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"
32+
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry"
2933
"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil"
30-
"github.com/containerd/nerdctl/v2/pkg/testutil/testregistry"
3134
)
3235

33-
func testMultiPlatformRun(base *testutil.Base, alpineImage string) {
34-
t := base.T
35-
testutil.RequireExecPlatform(t, "linux/amd64", "linux/arm64", "linux/arm/v7")
36+
// randomPort asks the registry helpers to acquire a free port automatically.
37+
const randomPort = 0
38+
39+
// requireMultiPlatformExec skips the test when the host cannot execute
40+
// linux/amd64, linux/arm64 and linux/arm/v7 images (e.g. no binfmt_misc).
41+
var requireMultiPlatformExec = &test.Requirement{
42+
Check: func(_ test.Data, _ test.Helpers) (bool, string) {
43+
ok, err := platformutil.CanExecProbably("linux/amd64", "linux/arm64", "linux/arm/v7")
44+
if !ok {
45+
msg := "requires multi-platform exec support (linux/amd64, linux/arm64, linux/arm/v7)"
46+
if err != nil {
47+
msg += ": " + err.Error()
48+
}
49+
return false, msg
50+
}
51+
return true, ""
52+
},
53+
}
54+
55+
// assertMultiPlatformRun runs uname -m inside image on each platform and
56+
// asserts the expected machine type string.
57+
func assertMultiPlatformRun(helpers test.Helpers, image string) {
3658
testCases := map[string]string{
3759
"amd64": "x86_64",
3860
"arm64": "aarch64",
@@ -41,92 +63,174 @@ func testMultiPlatformRun(base *testutil.Base, alpineImage string) {
4163
"linux/arm/v7": "armv7l",
4264
}
4365
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")
66+
helpers.T().Log(fmt.Sprintf("Testing platform %q (%q)", plat, expectedUnameM))
67+
helpers.Command("run", "--rm", "--platform="+plat, image, "uname", "-m").
68+
Run(&test.Expected{
69+
ExitCode: expect.ExitCodeSuccess,
70+
Output: expect.Equals(expectedUnameM + "\n"),
71+
})
4772
}
4873
}
4974

5075
func TestMultiPlatformRun(t *testing.T) {
51-
base := testutil.NewBase(t)
52-
testMultiPlatformRun(base, testutil.AlpineImage)
76+
testCase := nerdtest.Setup()
77+
78+
testCase.Require = requireMultiPlatformExec
79+
80+
testCase.Setup = func(_ test.Data, helpers test.Helpers) {
81+
assertMultiPlatformRun(helpers, testutil.AlpineImage)
82+
}
83+
84+
testCase.Run(t)
5385
}
5486

5587
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()
88+
testCase := nerdtest.Setup()
89+
90+
testCase.Require = require.All(
91+
// non-buildx `docker build` lacks multi-platform support; `docker push` lacks --platform
92+
require.Not(nerdtest.Docker),
93+
nerdtest.Build,
94+
requireMultiPlatformExec,
95+
nerdtest.Registry,
96+
)
97+
98+
var reg *registry.Server
99+
100+
testCase.Setup = func(data test.Data, helpers test.Helpers) {
101+
reg = nerdtest.RegistryWithNoAuth(data, helpers, randomPort, false)
102+
reg.Setup(data, helpers)
103+
imageName := fmt.Sprintf("localhost:%d/%s:latest", reg.Port, data.Identifier())
104+
data.Labels().Set("image", imageName)
105+
106+
dockerfile := fmt.Sprintf("FROM %s\nRUN echo dummy\n", testutil.AlpineImage)
107+
buildCtx := data.Temp().Dir()
108+
data.Temp().Save(dockerfile, "Dockerfile")
109+
110+
helpers.Ensure("build", "-t", imageName, "--platform=amd64,arm64,linux/arm/v7", buildCtx)
111+
}
112+
113+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
114+
if img := data.Labels().Get("image"); img != "" {
115+
helpers.Anyhow("rmi", img)
116+
}
117+
helpers.Anyhow("builder", "prune", "--all", "--force")
118+
if reg != nil {
119+
reg.Cleanup(data, helpers)
120+
}
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(expect.ExitCodeSuccess, 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+
// non-buildx `docker build` lacks multi-platform support; `docker push` lacks --platform
139+
require.Not(nerdtest.Docker),
140+
nerdtest.Build,
141+
requireMultiPlatformExec,
142+
nerdtest.Registry,
143+
)
144+
145+
var reg *registry.Server
146+
147+
testCase.Setup = func(data test.Data, helpers test.Helpers) {
148+
reg = nerdtest.RegistryWithNoAuth(data, helpers, randomPort, false)
149+
reg.Setup(data, helpers)
150+
imageName := fmt.Sprintf("localhost:%d/%s:latest", reg.Port, data.Identifier())
151+
data.Labels().Set("image", imageName)
152+
153+
dockerfile := fmt.Sprintf("FROM %s\nCMD echo dummy\n", testutil.AlpineImage)
154+
buildCtx := data.Temp().Dir()
155+
data.Temp().Save(dockerfile, "Dockerfile")
156+
157+
helpers.Ensure("build", "-t", imageName, "--platform=amd64,arm64,linux/arm/v7", buildCtx)
158+
}
159+
160+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
161+
if img := data.Labels().Get("image"); img != "" {
162+
helpers.Anyhow("rmi", img)
163+
}
164+
helpers.Anyhow("builder", "prune", "--all", "--force")
165+
if reg != nil {
166+
reg.Cleanup(data, helpers)
167+
}
168+
}
169+
170+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
171+
imageName := data.Labels().Get("image")
172+
assertMultiPlatformRun(helpers, imageName)
173+
return helpers.Command("push", "--platform=amd64,arm64,linux/arm/v7", imageName)
174+
}
175+
176+
testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, nil)
177+
178+
testCase.Run(t)
104179
}
105180

106181
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)
182+
testCase := nerdtest.Setup()
183+
184+
testCase.Require = require.All(
185+
require.Not(nerdtest.Docker),
186+
requireMultiPlatformExec,
187+
nerdtest.Registry,
188+
)
189+
190+
var reg *registry.Server
191+
192+
testCase.Setup = func(data test.Data, helpers test.Helpers) {
193+
reg = nerdtest.RegistryWithNoAuth(data, helpers, randomPort, false)
194+
reg.Setup(data, helpers)
195+
pushImageName := fmt.Sprintf("localhost:%d/%s:latest", reg.Port, data.Identifier())
196+
data.Labels().Set("image", pushImageName)
197+
helpers.Ensure("pull", "--quiet", "--all-platforms", testutil.AlpineImage)
198+
helpers.Ensure("tag", testutil.AlpineImage, pushImageName)
199+
}
200+
201+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
202+
if img := data.Labels().Get("image"); img != "" {
203+
helpers.Anyhow("rmi", img)
204+
}
205+
if reg != nil {
206+
reg.Cleanup(data, helpers)
207+
}
208+
}
209+
210+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
211+
pushImageName := data.Labels().Get("image")
212+
helpers.Ensure("push", "--all-platforms", pushImageName)
213+
assertMultiPlatformRun(helpers, pushImageName)
214+
return helpers.Command("inspect", "--type=image", pushImageName)
215+
}
216+
217+
testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, nil)
218+
219+
testCase.Run(t)
120220
}
121221

122222
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)
223+
testCase := nerdtest.Setup()
224+
225+
testCase.Require = require.All(
226+
require.Not(nerdtest.Docker),
227+
nerdtest.Build,
228+
requireMultiPlatformExec,
229+
)
128230

129-
const dockerComposeYAML = `
231+
testCase.Setup = func(data test.Data, helpers test.Helpers) {
232+
dockerfile := fmt.Sprintf("FROM %s\nRUN uname -m > /usr/share/nginx/html/index.html\n", testutil.NginxAlpineImage)
233+
composeYAML := `
130234
services:
131235
svc0:
132236
build: .
@@ -144,30 +248,49 @@ services:
144248
ports:
145249
- 8082:80
146250
`
147-
dockerfile := fmt.Sprintf(`FROM %s
148-
RUN uname -m > /usr/share/nginx/html/index.html
149-
`, testutil.NginxAlpineImage)
251+
buildCtx := data.Temp().Dir()
252+
composePath := data.Temp().Save(composeYAML, "compose.yaml")
253+
_ = buildCtx
254+
data.Temp().Save(dockerfile, "Dockerfile")
255+
data.Labels().Set("composePath", composePath)
150256

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()
257+
helpers.Ensure("compose", "-f", composePath, "up", "-d", "--build")
258+
}
158259

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",
260+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
261+
if cp := data.Labels().Get("composePath"); cp != "" {
262+
helpers.Anyhow("compose", "-f", cp, "down", "-v")
263+
}
264+
helpers.Anyhow("builder", "prune", "--all", "--force")
163265
}
164266

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))
267+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
268+
urlExpected := map[string]string{
269+
"http://127.0.0.1:8080": "x86_64",
270+
"http://127.0.0.1:8081": "aarch64",
271+
"http://127.0.0.1:8082": "armv7l",
272+
}
273+
for url, expected := range urlExpected {
274+
resp, err := nettestutil.HTTPGet(url, 5, false)
275+
if err != nil {
276+
helpers.T().Log(fmt.Sprintf("GET %s: %v", url, err))
277+
helpers.T().FailNow()
278+
}
279+
body, err := io.ReadAll(resp.Body)
280+
resp.Body.Close()
281+
if err != nil {
282+
helpers.T().Log(fmt.Sprintf("reading body from %s: %v", url, err))
283+
helpers.T().FailNow()
284+
}
285+
if !strings.Contains(string(body), expected) {
286+
helpers.T().Log(fmt.Sprintf("expected %q in body from %s, got %q", expected, url, string(body)))
287+
helpers.T().Fail()
288+
}
289+
}
290+
return helpers.Command("compose", "-f", data.Labels().Get("composePath"), "ps")
172291
}
292+
293+
testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, nil)
294+
295+
testCase.Run(t)
173296
}

0 commit comments

Comments
 (0)