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