Browse code

pkg/archive fixes, and port most unit tests to Windows

Signed-off-by: John Howard <jhoward@microsoft.com>

If fixes an error in sameFsTime which was using `==` to compare two times. The correct way is to use go's built-in timea.Equals(timeb).

In changes_windows, it uses sameFsTime to compare mTim of a `system.StatT` to allow TestChangesDirsMutated to operate correctly now.

Note there is slight different between the Linux and Windows implementations of detecting changes. Due to https://github.com/moby/moby/issues/9874,
and the fix at https://github.com/moby/moby/pull/11422, Linux does not consider a change to the directory time as a change. Windows on NTFS
does. See https://github.com/moby/moby/pull/37982 for more information. The result in `TestChangesDirsMutated`, `dir3` is NOT considered a change
in Linux, but IS considered a change on Windows. The test mutates dir3 to have a mtime of +1 second.

With a handful of tests still outstanding, this change ports most of the unit tests under pkg/archive to Windows.

It provides an implementation of `copyDir` in tests for Windows. To make a copy similar to Linux's `cp -a` while preserving timestamps
and links to both valid and invalid targets, xcopy isn't sufficient. So I used robocopy, but had to circumvent certain exit codes that
robocopy exits with which are warnings. Link to article describing this is in the code.

John Howard authored on 2018/10/06 07:46:59
Showing 7 changed files
... ...
@@ -305,7 +305,7 @@ func TestUntarPathWithInvalidSrc(t *testing.T) {
305 305
 }
306 306
 
307 307
 func TestUntarPath(t *testing.T) {
308
-	skip.If(t, os.Getuid() != 0, "skipping test that requires root")
308
+	skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root")
309 309
 	tmpFolder, err := ioutil.TempDir("", "docker-archive-test")
310 310
 	assert.NilError(t, err)
311 311
 	defer os.RemoveAll(tmpFolder)
... ...
@@ -436,7 +436,7 @@ func TestCopyWithTarInvalidSrc(t *testing.T) {
436 436
 }
437 437
 
438 438
 func TestCopyWithTarInexistentDestWillCreateIt(t *testing.T) {
439
-	skip.If(t, os.Getuid() != 0, "skipping test that requires root")
439
+	skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root")
440 440
 	tempFolder, err := ioutil.TempDir("", "docker-archive-test")
441 441
 	if err != nil {
442 442
 		t.Fatal(nil)
... ...
@@ -608,10 +608,6 @@ func TestCopyFileWithTarSrcFile(t *testing.T) {
608 608
 }
609 609
 
610 610
 func TestTarFiles(t *testing.T) {
611
-	// TODO Windows: Figure out how to port this test.
612
-	if runtime.GOOS == "windows" {
613
-		t.Skip("Failing on Windows")
614
-	}
615 611
 	// try without hardlinks
616 612
 	if err := checkNoChanges(1000, false); err != nil {
617 613
 		t.Fatal(err)
... ...
@@ -690,10 +686,6 @@ func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error
690 690
 }
691 691
 
692 692
 func TestTarUntar(t *testing.T) {
693
-	// TODO Windows: Figure out how to fix this test.
694
-	if runtime.GOOS == "windows" {
695
-		t.Skip("Failing on Windows")
696
-	}
697 693
 	origin, err := ioutil.TempDir("", "docker-test-untar-origin")
698 694
 	if err != nil {
699 695
 		t.Fatal(err)
... ...
@@ -722,7 +714,7 @@ func TestTarUntar(t *testing.T) {
722 722
 			t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
723 723
 		}
724 724
 
725
-		if len(changes) != 1 || changes[0].Path != "/3" {
725
+		if len(changes) != 1 || changes[0].Path != string(filepath.Separator)+"3" {
726 726
 			t.Fatalf("Unexpected differences after tarUntar: %v", changes)
727 727
 		}
728 728
 	}
... ...
@@ -780,10 +772,6 @@ func TestTarWithOptionsChownOptsAlwaysOverridesIdPair(t *testing.T) {
780 780
 }
781 781
 
782 782
 func TestTarWithOptions(t *testing.T) {
783
-	// TODO Windows: Figure out how to fix this test.
784
-	if runtime.GOOS == "windows" {
785
-		t.Skip("Failing on Windows")
786
-	}
787 783
 	origin, err := ioutil.TempDir("", "docker-test-untar-origin")
788 784
 	if err != nil {
789 785
 		t.Fatal(err)
... ...
@@ -942,10 +930,6 @@ func BenchmarkTarUntarWithLinks(b *testing.B) {
942 942
 }
943 943
 
944 944
 func TestUntarInvalidFilenames(t *testing.T) {
945
-	// TODO Windows: Figure out how to fix this test.
946
-	if runtime.GOOS == "windows" {
947
-		t.Skip("Passes but hits breakoutError: platform and architecture is not supported")
948
-	}
949 945
 	for i, headers := range [][]*tar.Header{
950 946
 		{
951 947
 			{
... ...
@@ -970,9 +954,7 @@ func TestUntarInvalidFilenames(t *testing.T) {
970 970
 }
971 971
 
972 972
 func TestUntarHardlinkToSymlink(t *testing.T) {
973
-	// TODO Windows. There may be a way of running this, but turning off for now
974
-	skip.If(t, runtime.GOOS == "windows", "hardlinks on Windows")
975
-	skip.If(t, os.Getuid() != 0, "skipping test that requires root")
973
+	skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root")
976 974
 	for i, headers := range [][]*tar.Header{
977 975
 		{
978 976
 			{
... ...
@@ -1001,10 +983,6 @@ func TestUntarHardlinkToSymlink(t *testing.T) {
1001 1001
 }
1002 1002
 
1003 1003
 func TestUntarInvalidHardlink(t *testing.T) {
1004
-	// TODO Windows. There may be a way of running this, but turning off for now
1005
-	if runtime.GOOS == "windows" {
1006
-		t.Skip("hardlinks on Windows")
1007
-	}
1008 1004
 	for i, headers := range [][]*tar.Header{
1009 1005
 		{ // try reading victim/hello (../)
1010 1006
 			{
... ...
@@ -1085,10 +1063,6 @@ func TestUntarInvalidHardlink(t *testing.T) {
1085 1085
 }
1086 1086
 
1087 1087
 func TestUntarInvalidSymlink(t *testing.T) {
1088
-	// TODO Windows. There may be a way of running this, but turning off for now
1089
-	if runtime.GOOS == "windows" {
1090
-		t.Skip("hardlinks on Windows")
1091
-	}
1092 1088
 	for i, headers := range [][]*tar.Header{
1093 1089
 		{ // try reading victim/hello (../)
1094 1090
 			{
... ...
@@ -1254,7 +1228,7 @@ func TestReplaceFileTarWrapper(t *testing.T) {
1254 1254
 // TestPrefixHeaderReadable tests that files that could be created with the
1255 1255
 // version of this package that was built with <=go17 are still readable.
1256 1256
 func TestPrefixHeaderReadable(t *testing.T) {
1257
-	skip.If(t, os.Getuid() != 0, "skipping test that requires root")
1257
+	skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root")
1258 1258
 	// https://gist.github.com/stevvooe/e2a790ad4e97425896206c0816e1a882#file-out-go
1259 1259
 	var testFile = []byte("\x1f\x8b\x08\x08\x44\x21\x68\x59\x00\x03\x74\x2e\x74\x61\x72\x00\x4b\xcb\xcf\x67\xa0\x35\x30\x80\x00\x86\x06\x10\x47\x01\xc1\x37\x40\x00\x54\xb6\xb1\xa1\xa9\x99\x09\x48\x25\x1d\x40\x69\x71\x49\x62\x91\x02\xe5\x76\xa1\x79\x84\x21\x91\xd6\x80\x72\xaf\x8f\x82\x51\x30\x0a\x46\x36\x00\x00\xf0\x1c\x1e\x95\x00\x06\x00\x00")
1260 1260
 
... ...
@@ -1312,7 +1286,7 @@ func appendModifier(path string, header *tar.Header, content io.Reader) (*tar.He
1312 1312
 }
1313 1313
 
1314 1314
 func readFileFromArchive(t *testing.T, archive io.ReadCloser, name string, expectedCount int, doc string) string {
1315
-	skip.If(t, os.Getuid() != 0, "skipping test that requires root")
1315
+	skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root")
1316 1316
 	destDir, err := ioutil.TempDir("", "docker-test-destDir")
1317 1317
 	assert.NilError(t, err)
1318 1318
 	defer os.RemoveAll(destDir)
... ...
@@ -63,12 +63,16 @@ func (c changesByPath) Less(i, j int) bool { return c[i].Path < c[j].Path }
63 63
 func (c changesByPath) Len() int           { return len(c) }
64 64
 func (c changesByPath) Swap(i, j int)      { c[j], c[i] = c[i], c[j] }
65 65
 
66
-// Gnu tar and the go tar writer don't have sub-second mtime
67
-// precision, which is problematic when we apply changes via tar
68
-// files, we handle this by comparing for exact times, *or* same
66
+// Gnu tar doesn't have sub-second mtime precision. The go tar
67
+// writer (1.10+) does when using PAX format, but we round times to seconds
68
+// to ensure archives have the same hashes for backwards compatibility.
69
+// See https://github.com/moby/moby/pull/35739/commits/fb170206ba12752214630b269a40ac7be6115ed4.
70
+//
71
+// Non-sub-second is problematic when we apply changes via tar
72
+// files. We handle this by comparing for exact times, *or* same
69 73
 // second count and either a or b having exactly 0 nanoseconds
70 74
 func sameFsTime(a, b time.Time) bool {
71
-	return a == b ||
75
+	return a.Equal(b) ||
72 76
 		(a.Unix() == b.Unix() &&
73 77
 			(a.Nanosecond() == 0 || b.Nanosecond() == 0))
74 78
 }
... ...
@@ -5,8 +5,10 @@ import (
5 5
 	"os"
6 6
 	"os/exec"
7 7
 	"path"
8
+	"path/filepath"
8 9
 	"runtime"
9 10
 	"sort"
11
+	"syscall"
10 12
 	"testing"
11 13
 	"time"
12 14
 
... ...
@@ -23,7 +25,24 @@ func max(x, y int) int {
23 23
 }
24 24
 
25 25
 func copyDir(src, dst string) error {
26
-	return exec.Command("cp", "-a", src, dst).Run()
26
+	if runtime.GOOS != "windows" {
27
+		return exec.Command("cp", "-a", src, dst).Run()
28
+	}
29
+
30
+	// Could have used xcopy src dst /E /I /H /Y /B. However, xcopy has the
31
+	// unfortunate side effect of not preserving timestamps of newly created
32
+	// directories in the target directory, so we don't get accurate changes.
33
+	// Use robocopy instead. Note this isn't available in microsoft/nanoserver.
34
+	// But it has gotchas. See https://weblogs.sqlteam.com/robv/archive/2010/02/17/61106.aspx
35
+	err := exec.Command("robocopy", filepath.FromSlash(src), filepath.FromSlash(dst), "/SL", "/COPYALL", "/MIR").Run()
36
+	if exiterr, ok := err.(*exec.ExitError); ok {
37
+		if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
38
+			if status.ExitStatus()&24 == 0 {
39
+				return nil
40
+			}
41
+		}
42
+	}
43
+	return err
27 44
 }
28 45
 
29 46
 type FileType uint32
... ...
@@ -113,11 +132,6 @@ func TestChangeString(t *testing.T) {
113 113
 }
114 114
 
115 115
 func TestChangesWithNoChanges(t *testing.T) {
116
-	// TODO Windows. There may be a way of running this, but turning off for now
117
-	// as createSampleDir uses symlinks.
118
-	if runtime.GOOS == "windows" {
119
-		t.Skip("symlinks on Windows")
120
-	}
121 116
 	rwLayer, err := ioutil.TempDir("", "docker-changes-test")
122 117
 	assert.NilError(t, err)
123 118
 	defer os.RemoveAll(rwLayer)
... ...
@@ -133,11 +147,6 @@ func TestChangesWithNoChanges(t *testing.T) {
133 133
 }
134 134
 
135 135
 func TestChangesWithChanges(t *testing.T) {
136
-	// TODO Windows. There may be a way of running this, but turning off for now
137
-	// as createSampleDir uses symlinks.
138
-	if runtime.GOOS == "windows" {
139
-		t.Skip("symlinks on Windows")
140
-	}
141 136
 	// Mock the readonly layer
142 137
 	layer, err := ioutil.TempDir("", "docker-changes-test-layer")
143 138
 	assert.NilError(t, err)
... ...
@@ -167,21 +176,20 @@ func TestChangesWithChanges(t *testing.T) {
167 167
 	assert.NilError(t, err)
168 168
 
169 169
 	expectedChanges := []Change{
170
-		{"/dir1", ChangeModify},
171
-		{"/dir1/file1-1", ChangeModify},
172
-		{"/dir1/file1-2", ChangeDelete},
173
-		{"/dir1/subfolder", ChangeModify},
174
-		{"/dir1/subfolder/newFile", ChangeAdd},
170
+		{filepath.FromSlash("/dir1"), ChangeModify},
171
+		{filepath.FromSlash("/dir1/file1-1"), ChangeModify},
172
+		{filepath.FromSlash("/dir1/file1-2"), ChangeDelete},
173
+		{filepath.FromSlash("/dir1/subfolder"), ChangeModify},
174
+		{filepath.FromSlash("/dir1/subfolder/newFile"), ChangeAdd},
175 175
 	}
176 176
 	checkChanges(expectedChanges, changes, t)
177 177
 }
178 178
 
179 179
 // See https://github.com/docker/docker/pull/13590
180 180
 func TestChangesWithChangesGH13590(t *testing.T) {
181
-	// TODO Windows. There may be a way of running this, but turning off for now
182
-	// as createSampleDir uses symlinks.
181
+	// TODO Windows. Needs further investigation to identify the failure
183 182
 	if runtime.GOOS == "windows" {
184
-		t.Skip("symlinks on Windows")
183
+		t.Skip("needs more investigation")
185 184
 	}
186 185
 	baseLayer, err := ioutil.TempDir("", "docker-changes-test.")
187 186
 	assert.NilError(t, err)
... ...
@@ -238,11 +246,6 @@ func TestChangesWithChangesGH13590(t *testing.T) {
238 238
 
239 239
 // Create a directory, copy it, make sure we report no changes between the two
240 240
 func TestChangesDirsEmpty(t *testing.T) {
241
-	// TODO Windows. There may be a way of running this, but turning off for now
242
-	// as createSampleDir uses symlinks.
243
-	if runtime.GOOS == "windows" {
244
-		t.Skip("symlinks on Windows")
245
-	}
246 241
 	src, err := ioutil.TempDir("", "docker-changes-test")
247 242
 	assert.NilError(t, err)
248 243
 	defer os.RemoveAll(src)
... ...
@@ -325,11 +328,6 @@ func mutateSampleDir(t *testing.T, root string) {
325 325
 }
326 326
 
327 327
 func TestChangesDirsMutated(t *testing.T) {
328
-	// TODO Windows. There may be a way of running this, but turning off for now
329
-	// as createSampleDir uses symlinks.
330
-	if runtime.GOOS == "windows" {
331
-		t.Skip("symlinks on Windows")
332
-	}
333 328
 	src, err := ioutil.TempDir("", "docker-changes-test")
334 329
 	assert.NilError(t, err)
335 330
 	createSampleDir(t, src)
... ...
@@ -347,20 +345,37 @@ func TestChangesDirsMutated(t *testing.T) {
347 347
 	sort.Sort(changesByPath(changes))
348 348
 
349 349
 	expectedChanges := []Change{
350
-		{"/dir1", ChangeDelete},
351
-		{"/dir2", ChangeModify},
352
-		{"/dirnew", ChangeAdd},
353
-		{"/file1", ChangeDelete},
354
-		{"/file2", ChangeModify},
355
-		{"/file3", ChangeModify},
356
-		{"/file4", ChangeModify},
357
-		{"/file5", ChangeModify},
358
-		{"/filenew", ChangeAdd},
359
-		{"/symlink1", ChangeDelete},
360
-		{"/symlink2", ChangeModify},
361
-		{"/symlinknew", ChangeAdd},
350
+		{filepath.FromSlash("/dir1"), ChangeDelete},
351
+		{filepath.FromSlash("/dir2"), ChangeModify},
362 352
 	}
363 353
 
354
+	// Note there is slight difference between the Linux and Windows
355
+	// implementations here. Due to https://github.com/moby/moby/issues/9874,
356
+	// and the fix at https://github.com/moby/moby/pull/11422, Linux does not
357
+	// consider a change to the directory time as a change. Windows on NTFS
358
+	// does. See https://github.com/moby/moby/pull/37982 for more information.
359
+	//
360
+	// Note also: https://github.com/moby/moby/pull/37982#discussion_r223523114
361
+	// that differences are ordered in the way the test is currently written, hence
362
+	// this is in the middle of the list of changes rather than at the start or
363
+	// end. Potentially can be addressed later.
364
+	if runtime.GOOS == "windows" {
365
+		expectedChanges = append(expectedChanges, Change{filepath.FromSlash("/dir3"), ChangeModify})
366
+	}
367
+
368
+	expectedChanges = append(expectedChanges, []Change{
369
+		{filepath.FromSlash("/dirnew"), ChangeAdd},
370
+		{filepath.FromSlash("/file1"), ChangeDelete},
371
+		{filepath.FromSlash("/file2"), ChangeModify},
372
+		{filepath.FromSlash("/file3"), ChangeModify},
373
+		{filepath.FromSlash("/file4"), ChangeModify},
374
+		{filepath.FromSlash("/file5"), ChangeModify},
375
+		{filepath.FromSlash("/filenew"), ChangeAdd},
376
+		{filepath.FromSlash("/symlink1"), ChangeDelete},
377
+		{filepath.FromSlash("/symlink2"), ChangeModify},
378
+		{filepath.FromSlash("/symlinknew"), ChangeAdd},
379
+	}...)
380
+
364 381
 	for i := 0; i < max(len(changes), len(expectedChanges)); i++ {
365 382
 		if i >= len(expectedChanges) {
366 383
 			t.Fatalf("unexpected change %s\n", changes[i].String())
... ...
@@ -373,7 +388,7 @@ func TestChangesDirsMutated(t *testing.T) {
373 373
 				t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String())
374 374
 			}
375 375
 		} else if changes[i].Path < expectedChanges[i].Path {
376
-			t.Fatalf("unexpected change %s\n", changes[i].String())
376
+			t.Fatalf("unexpected change %q %q\n", changes[i].String(), expectedChanges[i].Path)
377 377
 		} else {
378 378
 			t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String())
379 379
 		}
... ...
@@ -381,10 +396,13 @@ func TestChangesDirsMutated(t *testing.T) {
381 381
 }
382 382
 
383 383
 func TestApplyLayer(t *testing.T) {
384
-	// TODO Windows. There may be a way of running this, but turning off for now
385
-	// as createSampleDir uses symlinks.
384
+	// TODO Windows. This is very close to working, but it fails with changes
385
+	// to \symlinknew and \symlink2. The destination has an updated
386
+	// Access/Modify/Change/Birth date to the source (~3/100th sec different).
387
+	// Needs further investigation as to why, but I currently believe this is
388
+	// just the way NTFS works. I don't think it's a bug in this test or archive.
386 389
 	if runtime.GOOS == "windows" {
387
-		t.Skip("symlinks on Windows")
390
+		t.Skip("needs further investigation")
388 391
 	}
389 392
 	src, err := ioutil.TempDir("", "docker-changes-test")
390 393
 	assert.NilError(t, err)
... ...
@@ -417,10 +435,10 @@ func TestApplyLayer(t *testing.T) {
417 417
 }
418 418
 
419 419
 func TestChangesSizeWithHardlinks(t *testing.T) {
420
-	// TODO Windows. There may be a way of running this, but turning off for now
421
-	// as createSampleDir uses symlinks.
420
+	// TODO Windows. Needs further investigation. Likely in ChangeSizes not
421
+	// coping correctly with hardlinks on Windows.
422 422
 	if runtime.GOOS == "windows" {
423
-		t.Skip("hardlinks on Windows")
423
+		t.Skip("needs further investigation")
424 424
 	}
425 425
 	srcDir, err := ioutil.TempDir("", "docker-test-srcDir")
426 426
 	assert.NilError(t, err)
... ...
@@ -481,7 +499,7 @@ func TestChangesSize(t *testing.T) {
481 481
 }
482 482
 
483 483
 func checkChanges(expectedChanges, changes []Change, t *testing.T) {
484
-	skip.If(t, os.Getuid() != 0, "skipping test that requires root")
484
+	skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root")
485 485
 	sort.Sort(changesByPath(expectedChanges))
486 486
 	sort.Sort(changesByPath(changes))
487 487
 	for i := 0; i < max(len(changes), len(expectedChanges)); i++ {
... ...
@@ -16,7 +16,13 @@ func statDifferent(oldStat *system.StatT, newStat *system.StatT) bool {
16 16
 		oldStat.UID() != newStat.UID() ||
17 17
 		oldStat.GID() != newStat.GID() ||
18 18
 		oldStat.Rdev() != newStat.Rdev() ||
19
-		// Don't look at size for dirs, its not a good measure of change
19
+		// Don't look at size or modification time for dirs, its not a good
20
+		// measure of change. See https://github.com/moby/moby/issues/9874
21
+		// for a description of the issue with modification time, and
22
+		// https://github.com/moby/moby/pull/11422 for the change.
23
+		// (Note that in the Windows implementation of this function,
24
+		// modification time IS taken as a change). See
25
+		// https://github.com/moby/moby/pull/37982 for more information.
20 26
 		(oldStat.Mode()&unix.S_IFDIR != unix.S_IFDIR &&
21 27
 			(!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) || (oldStat.Size() != newStat.Size()))) {
22 28
 		return true
... ...
@@ -7,9 +7,13 @@ import (
7 7
 )
8 8
 
9 9
 func statDifferent(oldStat *system.StatT, newStat *system.StatT) bool {
10
+	// Note there is slight difference between the Linux and Windows
11
+	// implementations here. Due to https://github.com/moby/moby/issues/9874,
12
+	// and the fix at https://github.com/moby/moby/pull/11422, Linux does not
13
+	// consider a change to the directory time as a change. Windows on NTFS
14
+	// does. See https://github.com/moby/moby/pull/37982 for more information.
10 15
 
11
-	// Don't look at size for dirs, its not a good measure of change
12
-	if oldStat.Mtim() != newStat.Mtim() ||
16
+	if !sameFsTime(oldStat.Mtim(), newStat.Mtim()) ||
13 17
 		oldStat.Mode() != newStat.Mode() ||
14 18
 		oldStat.Size() != newStat.Size() && !oldStat.Mode().IsDir() {
15 19
 		return true
... ...
@@ -240,11 +240,13 @@ func applyLayerHandler(dest string, layer io.Reader, options *TarOptions, decomp
240 240
 	dest = filepath.Clean(dest)
241 241
 
242 242
 	// We need to be able to set any perms
243
-	oldmask, err := system.Umask(0)
244
-	if err != nil {
245
-		return 0, err
243
+	if runtime.GOOS != "windows" {
244
+		oldmask, err := system.Umask(0)
245
+		if err != nil {
246
+			return 0, err
247
+		}
248
+		defer system.Umask(oldmask)
246 249
 	}
247
-	defer system.Umask(oldmask) // ignore err, ErrNotSupportedPlatform
248 250
 
249 251
 	if decompress {
250 252
 		decompLayer, err := DecompressStream(layer)
... ...
@@ -7,17 +7,12 @@ import (
7 7
 	"os"
8 8
 	"path/filepath"
9 9
 	"reflect"
10
-	"runtime"
11 10
 	"testing"
12 11
 
13 12
 	"github.com/docker/docker/pkg/ioutils"
14 13
 )
15 14
 
16 15
 func TestApplyLayerInvalidFilenames(t *testing.T) {
17
-	// TODO Windows: Figure out how to fix this test.
18
-	if runtime.GOOS == "windows" {
19
-		t.Skip("Passes but hits breakoutError: platform and architecture is not supported")
20
-	}
21 16
 	for i, headers := range [][]*tar.Header{
22 17
 		{
23 18
 			{
... ...
@@ -42,9 +37,6 @@ func TestApplyLayerInvalidFilenames(t *testing.T) {
42 42
 }
43 43
 
44 44
 func TestApplyLayerInvalidHardlink(t *testing.T) {
45
-	if runtime.GOOS == "windows" {
46
-		t.Skip("TypeLink support on Windows")
47
-	}
48 45
 	for i, headers := range [][]*tar.Header{
49 46
 		{ // try reading victim/hello (../)
50 47
 			{
... ...
@@ -125,9 +117,6 @@ func TestApplyLayerInvalidHardlink(t *testing.T) {
125 125
 }
126 126
 
127 127
 func TestApplyLayerInvalidSymlink(t *testing.T) {
128
-	if runtime.GOOS == "windows" {
129
-		t.Skip("TypeSymLink support on Windows")
130
-	}
131 128
 	for i, headers := range [][]*tar.Header{
132 129
 		{ // try reading victim/hello (../)
133 130
 			{
... ...
@@ -208,11 +197,6 @@ func TestApplyLayerInvalidSymlink(t *testing.T) {
208 208
 }
209 209
 
210 210
 func TestApplyLayerWhiteouts(t *testing.T) {
211
-	// TODO Windows: Figure out why this test fails
212
-	if runtime.GOOS == "windows" {
213
-		t.Skip("Failing on Windows")
214
-	}
215
-
216 211
 	wd, err := ioutil.TempDir("", "graphdriver-test-whiteouts")
217 212
 	if err != nil {
218 213
 		return
... ...
@@ -339,7 +323,9 @@ func makeTestLayer(paths []string) (rc io.ReadCloser, err error) {
339 339
 		}
340 340
 	}()
341 341
 	for _, p := range paths {
342
-		if p[len(p)-1] == filepath.Separator {
342
+		// Source files are always in Unix format. But we use filepath on
343
+		// creation to be platform agnostic.
344
+		if p[len(p)-1] == '/' {
343 345
 			if err = os.MkdirAll(filepath.Join(tmpDir, p), 0700); err != nil {
344 346
 				return
345 347
 			}
... ...
@@ -374,9 +360,10 @@ func readDirContents(root string) ([]string, error) {
374 374
 			return err
375 375
 		}
376 376
 		if info.IsDir() {
377
-			rel = rel + "/"
377
+			rel = rel + string(filepath.Separator)
378 378
 		}
379
-		files = append(files, rel)
379
+		// Append in Unix semantics
380
+		files = append(files, filepath.ToSlash(rel))
380 381
 		return nil
381 382
 	})
382 383
 	if err != nil {