github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/pkg/archive/changes_test.go (about) 1 package archive 2 3 import ( 4 "io/ioutil" 5 "os" 6 "os/exec" 7 "path" 8 "runtime" 9 "sort" 10 "testing" 11 "time" 12 13 "github.com/docker/docker/pkg/system" 14 ) 15 16 func max(x, y int) int { 17 if x >= y { 18 return x 19 } 20 return y 21 } 22 23 func copyDir(src, dst string) error { 24 cmd := exec.Command("cp", "-a", src, dst) 25 if runtime.GOOS == "solaris" { 26 cmd = exec.Command("gcp", "-a", src, dst) 27 } 28 29 if err := cmd.Run(); err != nil { 30 return err 31 } 32 return nil 33 } 34 35 type FileType uint32 36 37 const ( 38 Regular FileType = iota 39 Dir 40 Symlink 41 ) 42 43 type FileData struct { 44 filetype FileType 45 path string 46 contents string 47 permissions os.FileMode 48 } 49 50 func createSampleDir(t *testing.T, root string) { 51 files := []FileData{ 52 {Regular, "file1", "file1\n", 0600}, 53 {Regular, "file2", "file2\n", 0666}, 54 {Regular, "file3", "file3\n", 0404}, 55 {Regular, "file4", "file4\n", 0600}, 56 {Regular, "file5", "file5\n", 0600}, 57 {Regular, "file6", "file6\n", 0600}, 58 {Regular, "file7", "file7\n", 0600}, 59 {Dir, "dir1", "", 0740}, 60 {Regular, "dir1/file1-1", "file1-1\n", 01444}, 61 {Regular, "dir1/file1-2", "file1-2\n", 0666}, 62 {Dir, "dir2", "", 0700}, 63 {Regular, "dir2/file2-1", "file2-1\n", 0666}, 64 {Regular, "dir2/file2-2", "file2-2\n", 0666}, 65 {Dir, "dir3", "", 0700}, 66 {Regular, "dir3/file3-1", "file3-1\n", 0666}, 67 {Regular, "dir3/file3-2", "file3-2\n", 0666}, 68 {Dir, "dir4", "", 0700}, 69 {Regular, "dir4/file3-1", "file4-1\n", 0666}, 70 {Regular, "dir4/file3-2", "file4-2\n", 0666}, 71 {Symlink, "symlink1", "target1", 0666}, 72 {Symlink, "symlink2", "target2", 0666}, 73 {Symlink, "symlink3", root + "/file1", 0666}, 74 {Symlink, "symlink4", root + "/symlink3", 0666}, 75 {Symlink, "dirSymlink", root + "/dir1", 0740}, 76 } 77 78 now := time.Now() 79 for _, info := range files { 80 p := path.Join(root, info.path) 81 if info.filetype == Dir { 82 if err := os.MkdirAll(p, info.permissions); err != nil { 83 t.Fatal(err) 84 } 85 } else if info.filetype == Regular { 86 if err := ioutil.WriteFile(p, []byte(info.contents), info.permissions); err != nil { 87 t.Fatal(err) 88 } 89 } else if info.filetype == Symlink { 90 if err := os.Symlink(info.contents, p); err != nil { 91 t.Fatal(err) 92 } 93 } 94 95 if info.filetype != Symlink { 96 // Set a consistent ctime, atime for all files and dirs 97 if err := system.Chtimes(p, now, now); err != nil { 98 t.Fatal(err) 99 } 100 } 101 } 102 } 103 104 func TestChangeString(t *testing.T) { 105 modifiyChange := Change{"change", ChangeModify} 106 toString := modifiyChange.String() 107 if toString != "C change" { 108 t.Fatalf("String() of a change with ChangeModifiy Kind should have been %s but was %s", "C change", toString) 109 } 110 addChange := Change{"change", ChangeAdd} 111 toString = addChange.String() 112 if toString != "A change" { 113 t.Fatalf("String() of a change with ChangeAdd Kind should have been %s but was %s", "A change", toString) 114 } 115 deleteChange := Change{"change", ChangeDelete} 116 toString = deleteChange.String() 117 if toString != "D change" { 118 t.Fatalf("String() of a change with ChangeDelete Kind should have been %s but was %s", "D change", toString) 119 } 120 } 121 122 func TestChangesWithNoChanges(t *testing.T) { 123 // TODO Windows. There may be a way of running this, but turning off for now 124 // as createSampleDir uses symlinks. 125 if runtime.GOOS == "windows" { 126 t.Skip("symlinks on Windows") 127 } 128 rwLayer, err := ioutil.TempDir("", "docker-changes-test") 129 if err != nil { 130 t.Fatal(err) 131 } 132 defer os.RemoveAll(rwLayer) 133 layer, err := ioutil.TempDir("", "docker-changes-test-layer") 134 if err != nil { 135 t.Fatal(err) 136 } 137 defer os.RemoveAll(layer) 138 createSampleDir(t, layer) 139 changes, err := Changes([]string{layer}, rwLayer) 140 if err != nil { 141 t.Fatal(err) 142 } 143 if len(changes) != 0 { 144 t.Fatalf("Changes with no difference should have detect no changes, but detected %d", len(changes)) 145 } 146 } 147 148 func TestChangesWithChanges(t *testing.T) { 149 // TODO Windows. There may be a way of running this, but turning off for now 150 // as createSampleDir uses symlinks. 151 if runtime.GOOS == "windows" { 152 t.Skip("symlinks on Windows") 153 } 154 // Mock the readonly layer 155 layer, err := ioutil.TempDir("", "docker-changes-test-layer") 156 if err != nil { 157 t.Fatal(err) 158 } 159 defer os.RemoveAll(layer) 160 createSampleDir(t, layer) 161 os.MkdirAll(path.Join(layer, "dir1/subfolder"), 0740) 162 163 // Mock the RW layer 164 rwLayer, err := ioutil.TempDir("", "docker-changes-test") 165 if err != nil { 166 t.Fatal(err) 167 } 168 defer os.RemoveAll(rwLayer) 169 170 // Create a folder in RW layer 171 dir1 := path.Join(rwLayer, "dir1") 172 os.MkdirAll(dir1, 0740) 173 deletedFile := path.Join(dir1, ".wh.file1-2") 174 ioutil.WriteFile(deletedFile, []byte{}, 0600) 175 modifiedFile := path.Join(dir1, "file1-1") 176 ioutil.WriteFile(modifiedFile, []byte{0x00}, 01444) 177 // Let's add a subfolder for a newFile 178 subfolder := path.Join(dir1, "subfolder") 179 os.MkdirAll(subfolder, 0740) 180 newFile := path.Join(subfolder, "newFile") 181 ioutil.WriteFile(newFile, []byte{}, 0740) 182 183 changes, err := Changes([]string{layer}, rwLayer) 184 if err != nil { 185 t.Fatal(err) 186 } 187 188 expectedChanges := []Change{ 189 {"/dir1", ChangeModify}, 190 {"/dir1/file1-1", ChangeModify}, 191 {"/dir1/file1-2", ChangeDelete}, 192 {"/dir1/subfolder", ChangeModify}, 193 {"/dir1/subfolder/newFile", ChangeAdd}, 194 } 195 checkChanges(expectedChanges, changes, t) 196 } 197 198 // See https://github.com/docker/docker/pull/13590 199 func TestChangesWithChangesGH13590(t *testing.T) { 200 // TODO Windows. There may be a way of running this, but turning off for now 201 // as createSampleDir uses symlinks. 202 if runtime.GOOS == "windows" { 203 t.Skip("symlinks on Windows") 204 } 205 baseLayer, err := ioutil.TempDir("", "docker-changes-test.") 206 defer os.RemoveAll(baseLayer) 207 208 dir3 := path.Join(baseLayer, "dir1/dir2/dir3") 209 os.MkdirAll(dir3, 07400) 210 211 file := path.Join(dir3, "file.txt") 212 ioutil.WriteFile(file, []byte("hello"), 0666) 213 214 layer, err := ioutil.TempDir("", "docker-changes-test2.") 215 defer os.RemoveAll(layer) 216 217 // Test creating a new file 218 if err := copyDir(baseLayer+"/dir1", layer+"/"); err != nil { 219 t.Fatalf("Cmd failed: %q", err) 220 } 221 222 os.Remove(path.Join(layer, "dir1/dir2/dir3/file.txt")) 223 file = path.Join(layer, "dir1/dir2/dir3/file1.txt") 224 ioutil.WriteFile(file, []byte("bye"), 0666) 225 226 changes, err := Changes([]string{baseLayer}, layer) 227 if err != nil { 228 t.Fatal(err) 229 } 230 231 expectedChanges := []Change{ 232 {"/dir1/dir2/dir3", ChangeModify}, 233 {"/dir1/dir2/dir3/file1.txt", ChangeAdd}, 234 } 235 checkChanges(expectedChanges, changes, t) 236 237 // Now test changing a file 238 layer, err = ioutil.TempDir("", "docker-changes-test3.") 239 defer os.RemoveAll(layer) 240 241 if err := copyDir(baseLayer+"/dir1", layer+"/"); err != nil { 242 t.Fatalf("Cmd failed: %q", err) 243 } 244 245 file = path.Join(layer, "dir1/dir2/dir3/file.txt") 246 ioutil.WriteFile(file, []byte("bye"), 0666) 247 248 changes, err = Changes([]string{baseLayer}, layer) 249 if err != nil { 250 t.Fatal(err) 251 } 252 253 expectedChanges = []Change{ 254 {"/dir1/dir2/dir3/file.txt", ChangeModify}, 255 } 256 checkChanges(expectedChanges, changes, t) 257 } 258 259 // Create a directory, copy it, make sure we report no changes between the two 260 func TestChangesDirsEmpty(t *testing.T) { 261 // TODO Windows. There may be a way of running this, but turning off for now 262 // as createSampleDir uses symlinks. 263 // TODO Should work for Solaris 264 if runtime.GOOS == "windows" || runtime.GOOS == "solaris" { 265 t.Skip("symlinks on Windows; gcp failure on Solaris") 266 } 267 src, err := ioutil.TempDir("", "docker-changes-test") 268 if err != nil { 269 t.Fatal(err) 270 } 271 defer os.RemoveAll(src) 272 createSampleDir(t, src) 273 dst := src + "-copy" 274 if err := copyDir(src, dst); err != nil { 275 t.Fatal(err) 276 } 277 defer os.RemoveAll(dst) 278 changes, err := ChangesDirs(dst, src) 279 if err != nil { 280 t.Fatal(err) 281 } 282 283 if len(changes) != 0 { 284 t.Fatalf("Reported changes for identical dirs: %v", changes) 285 } 286 os.RemoveAll(src) 287 os.RemoveAll(dst) 288 } 289 290 func mutateSampleDir(t *testing.T, root string) { 291 // Remove a regular file 292 if err := os.RemoveAll(path.Join(root, "file1")); err != nil { 293 t.Fatal(err) 294 } 295 296 // Remove a directory 297 if err := os.RemoveAll(path.Join(root, "dir1")); err != nil { 298 t.Fatal(err) 299 } 300 301 // Remove a symlink 302 if err := os.RemoveAll(path.Join(root, "symlink1")); err != nil { 303 t.Fatal(err) 304 } 305 306 // Rewrite a file 307 if err := ioutil.WriteFile(path.Join(root, "file2"), []byte("fileNN\n"), 0777); err != nil { 308 t.Fatal(err) 309 } 310 311 // Replace a file 312 if err := os.RemoveAll(path.Join(root, "file3")); err != nil { 313 t.Fatal(err) 314 } 315 if err := ioutil.WriteFile(path.Join(root, "file3"), []byte("fileMM\n"), 0404); err != nil { 316 t.Fatal(err) 317 } 318 319 // Touch file 320 if err := system.Chtimes(path.Join(root, "file4"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil { 321 t.Fatal(err) 322 } 323 324 // Replace file with dir 325 if err := os.RemoveAll(path.Join(root, "file5")); err != nil { 326 t.Fatal(err) 327 } 328 if err := os.MkdirAll(path.Join(root, "file5"), 0666); err != nil { 329 t.Fatal(err) 330 } 331 332 // Create new file 333 if err := ioutil.WriteFile(path.Join(root, "filenew"), []byte("filenew\n"), 0777); err != nil { 334 t.Fatal(err) 335 } 336 337 // Create new dir 338 if err := os.MkdirAll(path.Join(root, "dirnew"), 0766); err != nil { 339 t.Fatal(err) 340 } 341 342 // Create a new symlink 343 if err := os.Symlink("targetnew", path.Join(root, "symlinknew")); err != nil { 344 t.Fatal(err) 345 } 346 347 // Change a symlink 348 if err := os.RemoveAll(path.Join(root, "symlink2")); err != nil { 349 t.Fatal(err) 350 } 351 if err := os.Symlink("target2change", path.Join(root, "symlink2")); err != nil { 352 t.Fatal(err) 353 } 354 355 // Replace dir with file 356 if err := os.RemoveAll(path.Join(root, "dir2")); err != nil { 357 t.Fatal(err) 358 } 359 if err := ioutil.WriteFile(path.Join(root, "dir2"), []byte("dir2\n"), 0777); err != nil { 360 t.Fatal(err) 361 } 362 363 // Touch dir 364 if err := system.Chtimes(path.Join(root, "dir3"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil { 365 t.Fatal(err) 366 } 367 } 368 369 func TestChangesDirsMutated(t *testing.T) { 370 // TODO Windows. There may be a way of running this, but turning off for now 371 // as createSampleDir uses symlinks. 372 // TODO Should work for Solaris 373 if runtime.GOOS == "windows" || runtime.GOOS == "solaris" { 374 t.Skip("symlinks on Windows; gcp failures on Solaris") 375 } 376 src, err := ioutil.TempDir("", "docker-changes-test") 377 if err != nil { 378 t.Fatal(err) 379 } 380 createSampleDir(t, src) 381 dst := src + "-copy" 382 if err := copyDir(src, dst); err != nil { 383 t.Fatal(err) 384 } 385 defer os.RemoveAll(src) 386 defer os.RemoveAll(dst) 387 388 mutateSampleDir(t, dst) 389 390 changes, err := ChangesDirs(dst, src) 391 if err != nil { 392 t.Fatal(err) 393 } 394 395 sort.Sort(changesByPath(changes)) 396 397 expectedChanges := []Change{ 398 {"/dir1", ChangeDelete}, 399 {"/dir2", ChangeModify}, 400 {"/dirnew", ChangeAdd}, 401 {"/file1", ChangeDelete}, 402 {"/file2", ChangeModify}, 403 {"/file3", ChangeModify}, 404 {"/file4", ChangeModify}, 405 {"/file5", ChangeModify}, 406 {"/filenew", ChangeAdd}, 407 {"/symlink1", ChangeDelete}, 408 {"/symlink2", ChangeModify}, 409 {"/symlinknew", ChangeAdd}, 410 } 411 412 for i := 0; i < max(len(changes), len(expectedChanges)); i++ { 413 if i >= len(expectedChanges) { 414 t.Fatalf("unexpected change %s\n", changes[i].String()) 415 } 416 if i >= len(changes) { 417 t.Fatalf("no change for expected change %s\n", expectedChanges[i].String()) 418 } 419 if changes[i].Path == expectedChanges[i].Path { 420 if changes[i] != expectedChanges[i] { 421 t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String()) 422 } 423 } else if changes[i].Path < expectedChanges[i].Path { 424 t.Fatalf("unexpected change %s\n", changes[i].String()) 425 } else { 426 t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String()) 427 } 428 } 429 } 430 431 func TestApplyLayer(t *testing.T) { 432 // TODO Windows. There may be a way of running this, but turning off for now 433 // as createSampleDir uses symlinks. 434 // TODO Should work for Solaris 435 if runtime.GOOS == "windows" || runtime.GOOS == "solaris" { 436 t.Skip("symlinks on Windows; gcp failures on Solaris") 437 } 438 src, err := ioutil.TempDir("", "docker-changes-test") 439 if err != nil { 440 t.Fatal(err) 441 } 442 createSampleDir(t, src) 443 defer os.RemoveAll(src) 444 dst := src + "-copy" 445 if err := copyDir(src, dst); err != nil { 446 t.Fatal(err) 447 } 448 mutateSampleDir(t, dst) 449 defer os.RemoveAll(dst) 450 451 changes, err := ChangesDirs(dst, src) 452 if err != nil { 453 t.Fatal(err) 454 } 455 456 layer, err := ExportChanges(dst, changes, nil, nil) 457 if err != nil { 458 t.Fatal(err) 459 } 460 461 layerCopy, err := NewTempArchive(layer, "") 462 if err != nil { 463 t.Fatal(err) 464 } 465 466 if _, err := ApplyLayer(src, layerCopy); err != nil { 467 t.Fatal(err) 468 } 469 470 changes2, err := ChangesDirs(src, dst) 471 if err != nil { 472 t.Fatal(err) 473 } 474 475 if len(changes2) != 0 { 476 t.Fatalf("Unexpected differences after reapplying mutation: %v", changes2) 477 } 478 } 479 480 func TestChangesSizeWithHardlinks(t *testing.T) { 481 // TODO Windows. There may be a way of running this, but turning off for now 482 // as createSampleDir uses symlinks. 483 if runtime.GOOS == "windows" { 484 t.Skip("hardlinks on Windows") 485 } 486 srcDir, err := ioutil.TempDir("", "docker-test-srcDir") 487 if err != nil { 488 t.Fatal(err) 489 } 490 defer os.RemoveAll(srcDir) 491 492 destDir, err := ioutil.TempDir("", "docker-test-destDir") 493 if err != nil { 494 t.Fatal(err) 495 } 496 defer os.RemoveAll(destDir) 497 498 creationSize, err := prepareUntarSourceDirectory(100, destDir, true) 499 if err != nil { 500 t.Fatal(err) 501 } 502 503 changes, err := ChangesDirs(destDir, srcDir) 504 if err != nil { 505 t.Fatal(err) 506 } 507 508 got := ChangesSize(destDir, changes) 509 if got != int64(creationSize) { 510 t.Errorf("Expected %d bytes of changes, got %d", creationSize, got) 511 } 512 } 513 514 func TestChangesSizeWithNoChanges(t *testing.T) { 515 size := ChangesSize("/tmp", nil) 516 if size != 0 { 517 t.Fatalf("ChangesSizes with no changes should be 0, was %d", size) 518 } 519 } 520 521 func TestChangesSizeWithOnlyDeleteChanges(t *testing.T) { 522 changes := []Change{ 523 {Path: "deletedPath", Kind: ChangeDelete}, 524 } 525 size := ChangesSize("/tmp", changes) 526 if size != 0 { 527 t.Fatalf("ChangesSizes with only delete changes should be 0, was %d", size) 528 } 529 } 530 531 func TestChangesSize(t *testing.T) { 532 parentPath, err := ioutil.TempDir("", "docker-changes-test") 533 defer os.RemoveAll(parentPath) 534 addition := path.Join(parentPath, "addition") 535 if err := ioutil.WriteFile(addition, []byte{0x01, 0x01, 0x01}, 0744); err != nil { 536 t.Fatal(err) 537 } 538 modification := path.Join(parentPath, "modification") 539 if err = ioutil.WriteFile(modification, []byte{0x01, 0x01, 0x01}, 0744); err != nil { 540 t.Fatal(err) 541 } 542 changes := []Change{ 543 {Path: "addition", Kind: ChangeAdd}, 544 {Path: "modification", Kind: ChangeModify}, 545 } 546 size := ChangesSize(parentPath, changes) 547 if size != 6 { 548 t.Fatalf("Expected 6 bytes of changes, got %d", size) 549 } 550 } 551 552 func checkChanges(expectedChanges, changes []Change, t *testing.T) { 553 sort.Sort(changesByPath(expectedChanges)) 554 sort.Sort(changesByPath(changes)) 555 for i := 0; i < max(len(changes), len(expectedChanges)); i++ { 556 if i >= len(expectedChanges) { 557 t.Fatalf("unexpected change %s\n", changes[i].String()) 558 } 559 if i >= len(changes) { 560 t.Fatalf("no change for expected change %s\n", expectedChanges[i].String()) 561 } 562 if changes[i].Path == expectedChanges[i].Path { 563 if changes[i] != expectedChanges[i] { 564 t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String()) 565 } 566 } else if changes[i].Path < expectedChanges[i].Path { 567 t.Fatalf("unexpected change %s\n", changes[i].String()) 568 } else { 569 t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String()) 570 } 571 } 572 }