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.
| ... | ... |
@@ -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 {
|