@@ -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
5070func 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
5594func 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\n RUN 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.
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+ 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\n CMD 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
106174func 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
122209func 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\n RUN uname -m > /usr/share/nginx/html/index.html\n " , testutil .NginxAlpineImage )
220+ composeYAML := `
130221services:
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