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