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