@@ -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
5075func 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
5587func 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\n RUN 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.
82134func 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\n CMD 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
106181func 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
122222func 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\n RUN uname -m > /usr/share/nginx/html/index.html\n " , testutil .NginxAlpineImage )
233+ composeYAML := `
130234services:
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