github.com/jogo/docker@v1.7.0-rc1/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 "syscall" 10 "testing" 11 "time" 12 ) 13 14 func max(x, y int) int { 15 if x >= y { 16 return x 17 } 18 return y 19 } 20 21 func copyDir(src, dst string) error { 22 cmd := exec.Command("cp", "-a", src, dst) 23 if err := cmd.Run(); err != nil { 24 return err 25 } 26 return nil 27 } 28 29 type FileType uint32 30 31 const ( 32 Regular FileType = iota 33 Dir 34 Symlink 35 ) 36 37 type FileData struct { 38 filetype FileType 39 path string 40 contents string 41 permissions os.FileMode 42 } 43 44 func createSampleDir(t *testing.T, root string) { 45 files := []FileData{ 46 {Regular, "file1", "file1\n", 0600}, 47 {Regular, "file2", "file2\n", 0666}, 48 {Regular, "file3", "file3\n", 0404}, 49 {Regular, "file4", "file4\n", 0600}, 50 {Regular, "file5", "file5\n", 0600}, 51 {Regular, "file6", "file6\n", 0600}, 52 {Regular, "file7", "file7\n", 0600}, 53 {Dir, "dir1", "", 0740}, 54 {Regular, "dir1/file1-1", "file1-1\n", 01444}, 55 {Regular, "dir1/file1-2", "file1-2\n", 0666}, 56 {Dir, "dir2", "", 0700}, 57 {Regular, "dir2/file2-1", "file2-1\n", 0666}, 58 {Regular, "dir2/file2-2", "file2-2\n", 0666}, 59 {Dir, "dir3", "", 0700}, 60 {Regular, "dir3/file3-1", "file3-1\n", 0666}, 61 {Regular, "dir3/file3-2", "file3-2\n", 0666}, 62 {Dir, "dir4", "", 0700}, 63 {Regular, "dir4/file3-1", "file4-1\n", 0666}, 64 {Regular, "dir4/file3-2", "file4-2\n", 0666}, 65 {Symlink, "symlink1", "target1", 0666}, 66 {Symlink, "symlink2", "target2", 0666}, 67 } 68 69 now := time.Now() 70 for _, info := range files { 71 p := path.Join(root, info.path) 72 if info.filetype == Dir { 73 if err := os.MkdirAll(p, info.permissions); err != nil { 74 t.Fatal(err) 75 } 76 } else if info.filetype == Regular { 77 if err := ioutil.WriteFile(p, []byte(info.contents), info.permissions); err != nil { 78 t.Fatal(err) 79 } 80 } else if info.filetype == Symlink { 81 if err := os.Symlink(info.contents, p); err != nil { 82 t.Fatal(err) 83 } 84 } 85 86 if info.filetype != Symlink { 87 // Set a consistent ctime, atime for all files and dirs 88 if err := os.Chtimes(p, now, now); err != nil { 89 t.Fatal(err) 90 } 91 } 92 } 93 } 94 95 func TestChangeString(t *testing.T) { 96 modifiyChange := Change{"change", ChangeModify} 97 toString := modifiyChange.String() 98 if toString != "C change" { 99 t.Fatalf("String() of a change with ChangeModifiy Kind should have been %s but was %s", "C change", toString) 100 } 101 addChange := Change{"change", ChangeAdd} 102 toString = addChange.String() 103 if toString != "A change" { 104 t.Fatalf("String() of a change with ChangeAdd Kind should have been %s but was %s", "A change", toString) 105 } 106 deleteChange := Change{"change", ChangeDelete} 107 toString = deleteChange.String() 108 if toString != "D change" { 109 t.Fatalf("String() of a change with ChangeDelete Kind should have been %s but was %s", "D change", toString) 110 } 111 } 112 113 func TestChangesWithNoChanges(t *testing.T) { 114 rwLayer, err := ioutil.TempDir("", "docker-changes-test") 115 if err != nil { 116 t.Fatal(err) 117 } 118 defer os.RemoveAll(rwLayer) 119 layer, err := ioutil.TempDir("", "docker-changes-test-layer") 120 if err != nil { 121 t.Fatal(err) 122 } 123 defer os.RemoveAll(layer) 124 createSampleDir(t, layer) 125 changes, err := Changes([]string{layer}, rwLayer) 126 if err != nil { 127 t.Fatal(err) 128 } 129 if len(changes) != 0 { 130 t.Fatalf("Changes with no difference should have detect no changes, but detected %d", len(changes)) 131 } 132 } 133 134 func TestChangesWithChanges(t *testing.T) { 135 rwLayer, err := ioutil.TempDir("", "docker-changes-test") 136 if err != nil { 137 t.Fatal(err) 138 } 139 defer os.RemoveAll(rwLayer) 140 // Create a folder 141 dir1 := path.Join(rwLayer, "dir1") 142 os.MkdirAll(dir1, 0740) 143 deletedFile := path.Join(dir1, ".wh.file1-2") 144 ioutil.WriteFile(deletedFile, []byte{}, 0600) 145 modifiedFile := path.Join(dir1, "file1-1") 146 ioutil.WriteFile(modifiedFile, []byte{0x00}, 01444) 147 // Let's add a subfolder for a newFile 148 subfolder := path.Join(dir1, "subfolder") 149 os.MkdirAll(subfolder, 0740) 150 newFile := path.Join(subfolder, "newFile") 151 ioutil.WriteFile(newFile, []byte{}, 0740) 152 // Let's create folders that with have the role of layers with the same data 153 layer, err := ioutil.TempDir("", "docker-changes-test-layer") 154 if err != nil { 155 t.Fatal(err) 156 } 157 defer os.RemoveAll(layer) 158 createSampleDir(t, layer) 159 os.MkdirAll(path.Join(layer, "dir1/subfolder"), 0740) 160 161 // Let's modify modtime for dir1 to be sure it's the same for the two layer (to not having false positive) 162 fi, err := os.Stat(dir1) 163 if err != nil { 164 return 165 } 166 mtime := fi.ModTime() 167 stat := fi.Sys().(*syscall.Stat_t) 168 atime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) 169 170 layerDir1 := path.Join(layer, "dir1") 171 os.Chtimes(layerDir1, atime, mtime) 172 173 changes, err := Changes([]string{layer}, rwLayer) 174 if err != nil { 175 t.Fatal(err) 176 } 177 178 sort.Sort(changesByPath(changes)) 179 180 expectedChanges := []Change{ 181 {"/dir1/file1-1", ChangeModify}, 182 {"/dir1/file1-2", ChangeDelete}, 183 {"/dir1/subfolder", ChangeModify}, 184 {"/dir1/subfolder/newFile", ChangeAdd}, 185 } 186 187 for i := 0; i < max(len(changes), len(expectedChanges)); i++ { 188 if i >= len(expectedChanges) { 189 t.Fatalf("unexpected change %s\n", changes[i].String()) 190 } 191 if i >= len(changes) { 192 t.Fatalf("no change for expected change %s\n", expectedChanges[i].String()) 193 } 194 if changes[i].Path == expectedChanges[i].Path { 195 if changes[i] != expectedChanges[i] { 196 t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String()) 197 } 198 } else if changes[i].Path < expectedChanges[i].Path { 199 t.Fatalf("unexpected change %s\n", changes[i].String()) 200 } else { 201 t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String()) 202 } 203 } 204 } 205 206 // Create an directory, copy it, make sure we report no changes between the two 207 func TestChangesDirsEmpty(t *testing.T) { 208 src, err := ioutil.TempDir("", "docker-changes-test") 209 if err != nil { 210 t.Fatal(err) 211 } 212 defer os.RemoveAll(src) 213 createSampleDir(t, src) 214 dst := src + "-copy" 215 if err := copyDir(src, dst); err != nil { 216 t.Fatal(err) 217 } 218 defer os.RemoveAll(dst) 219 changes, err := ChangesDirs(dst, src) 220 if err != nil { 221 t.Fatal(err) 222 } 223 224 if len(changes) != 0 { 225 t.Fatalf("Reported changes for identical dirs: %v", changes) 226 } 227 os.RemoveAll(src) 228 os.RemoveAll(dst) 229 } 230 231 func mutateSampleDir(t *testing.T, root string) { 232 // Remove a regular file 233 if err := os.RemoveAll(path.Join(root, "file1")); err != nil { 234 t.Fatal(err) 235 } 236 237 // Remove a directory 238 if err := os.RemoveAll(path.Join(root, "dir1")); err != nil { 239 t.Fatal(err) 240 } 241 242 // Remove a symlink 243 if err := os.RemoveAll(path.Join(root, "symlink1")); err != nil { 244 t.Fatal(err) 245 } 246 247 // Rewrite a file 248 if err := ioutil.WriteFile(path.Join(root, "file2"), []byte("fileNN\n"), 0777); err != nil { 249 t.Fatal(err) 250 } 251 252 // Replace a file 253 if err := os.RemoveAll(path.Join(root, "file3")); err != nil { 254 t.Fatal(err) 255 } 256 if err := ioutil.WriteFile(path.Join(root, "file3"), []byte("fileMM\n"), 0404); err != nil { 257 t.Fatal(err) 258 } 259 260 // Touch file 261 if err := os.Chtimes(path.Join(root, "file4"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil { 262 t.Fatal(err) 263 } 264 265 // Replace file with dir 266 if err := os.RemoveAll(path.Join(root, "file5")); err != nil { 267 t.Fatal(err) 268 } 269 if err := os.MkdirAll(path.Join(root, "file5"), 0666); err != nil { 270 t.Fatal(err) 271 } 272 273 // Create new file 274 if err := ioutil.WriteFile(path.Join(root, "filenew"), []byte("filenew\n"), 0777); err != nil { 275 t.Fatal(err) 276 } 277 278 // Create new dir 279 if err := os.MkdirAll(path.Join(root, "dirnew"), 0766); err != nil { 280 t.Fatal(err) 281 } 282 283 // Create a new symlink 284 if err := os.Symlink("targetnew", path.Join(root, "symlinknew")); err != nil { 285 t.Fatal(err) 286 } 287 288 // Change a symlink 289 if err := os.RemoveAll(path.Join(root, "symlink2")); err != nil { 290 t.Fatal(err) 291 } 292 if err := os.Symlink("target2change", path.Join(root, "symlink2")); err != nil { 293 t.Fatal(err) 294 } 295 296 // Replace dir with file 297 if err := os.RemoveAll(path.Join(root, "dir2")); err != nil { 298 t.Fatal(err) 299 } 300 if err := ioutil.WriteFile(path.Join(root, "dir2"), []byte("dir2\n"), 0777); err != nil { 301 t.Fatal(err) 302 } 303 304 // Touch dir 305 if err := os.Chtimes(path.Join(root, "dir3"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil { 306 t.Fatal(err) 307 } 308 } 309 310 func TestChangesDirsMutated(t *testing.T) { 311 src, err := ioutil.TempDir("", "docker-changes-test") 312 if err != nil { 313 t.Fatal(err) 314 } 315 createSampleDir(t, src) 316 dst := src + "-copy" 317 if err := copyDir(src, dst); err != nil { 318 t.Fatal(err) 319 } 320 defer os.RemoveAll(src) 321 defer os.RemoveAll(dst) 322 323 mutateSampleDir(t, dst) 324 325 changes, err := ChangesDirs(dst, src) 326 if err != nil { 327 t.Fatal(err) 328 } 329 330 sort.Sort(changesByPath(changes)) 331 332 expectedChanges := []Change{ 333 {"/dir1", ChangeDelete}, 334 {"/dir2", ChangeModify}, 335 {"/dirnew", ChangeAdd}, 336 {"/file1", ChangeDelete}, 337 {"/file2", ChangeModify}, 338 {"/file3", ChangeModify}, 339 {"/file4", ChangeModify}, 340 {"/file5", ChangeModify}, 341 {"/filenew", ChangeAdd}, 342 {"/symlink1", ChangeDelete}, 343 {"/symlink2", ChangeModify}, 344 {"/symlinknew", ChangeAdd}, 345 } 346 347 for i := 0; i < max(len(changes), len(expectedChanges)); i++ { 348 if i >= len(expectedChanges) { 349 t.Fatalf("unexpected change %s\n", changes[i].String()) 350 } 351 if i >= len(changes) { 352 t.Fatalf("no change for expected change %s\n", expectedChanges[i].String()) 353 } 354 if changes[i].Path == expectedChanges[i].Path { 355 if changes[i] != expectedChanges[i] { 356 t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String()) 357 } 358 } else if changes[i].Path < expectedChanges[i].Path { 359 t.Fatalf("unexpected change %s\n", changes[i].String()) 360 } else { 361 t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String()) 362 } 363 } 364 } 365 366 func TestApplyLayer(t *testing.T) { 367 src, err := ioutil.TempDir("", "docker-changes-test") 368 if err != nil { 369 t.Fatal(err) 370 } 371 createSampleDir(t, src) 372 defer os.RemoveAll(src) 373 dst := src + "-copy" 374 if err := copyDir(src, dst); err != nil { 375 t.Fatal(err) 376 } 377 mutateSampleDir(t, dst) 378 defer os.RemoveAll(dst) 379 380 changes, err := ChangesDirs(dst, src) 381 if err != nil { 382 t.Fatal(err) 383 } 384 385 layer, err := ExportChanges(dst, changes) 386 if err != nil { 387 t.Fatal(err) 388 } 389 390 layerCopy, err := NewTempArchive(layer, "") 391 if err != nil { 392 t.Fatal(err) 393 } 394 395 if _, err := ApplyLayer(src, layerCopy); err != nil { 396 t.Fatal(err) 397 } 398 399 changes2, err := ChangesDirs(src, dst) 400 if err != nil { 401 t.Fatal(err) 402 } 403 404 if len(changes2) != 0 { 405 t.Fatalf("Unexpected differences after reapplying mutation: %v", changes2) 406 } 407 } 408 409 func TestChangesSizeWithNoChanges(t *testing.T) { 410 size := ChangesSize("/tmp", nil) 411 if size != 0 { 412 t.Fatalf("ChangesSizes with no changes should be 0, was %d", size) 413 } 414 } 415 416 func TestChangesSizeWithOnlyDeleteChanges(t *testing.T) { 417 changes := []Change{ 418 {Path: "deletedPath", Kind: ChangeDelete}, 419 } 420 size := ChangesSize("/tmp", changes) 421 if size != 0 { 422 t.Fatalf("ChangesSizes with only delete changes should be 0, was %d", size) 423 } 424 } 425 426 func TestChangesSize(t *testing.T) { 427 parentPath, err := ioutil.TempDir("", "docker-changes-test") 428 defer os.RemoveAll(parentPath) 429 addition := path.Join(parentPath, "addition") 430 if err := ioutil.WriteFile(addition, []byte{0x01, 0x01, 0x01}, 0744); err != nil { 431 t.Fatal(err) 432 } 433 modification := path.Join(parentPath, "modification") 434 if err = ioutil.WriteFile(modification, []byte{0x01, 0x01, 0x01}, 0744); err != nil { 435 t.Fatal(err) 436 } 437 changes := []Change{ 438 {Path: "addition", Kind: ChangeAdd}, 439 {Path: "modification", Kind: ChangeModify}, 440 } 441 size := ChangesSize(parentPath, changes) 442 if size != 6 { 443 t.Fatalf("ChangesSizes with only delete changes should be 0, was %d", size) 444 } 445 }