github.com/miqui/docker@v1.9.1/pkg/archive/copy_test.go (about) 1 package archive 2 3 import ( 4 "bytes" 5 "crypto/sha256" 6 "encoding/hex" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "os" 11 "path/filepath" 12 "strings" 13 "testing" 14 ) 15 16 func removeAllPaths(paths ...string) { 17 for _, path := range paths { 18 os.RemoveAll(path) 19 } 20 } 21 22 func getTestTempDirs(t *testing.T) (tmpDirA, tmpDirB string) { 23 var err error 24 25 if tmpDirA, err = ioutil.TempDir("", "archive-copy-test"); err != nil { 26 t.Fatal(err) 27 } 28 29 if tmpDirB, err = ioutil.TempDir("", "archive-copy-test"); err != nil { 30 t.Fatal(err) 31 } 32 33 return 34 } 35 36 func isNotDir(err error) bool { 37 return strings.Contains(err.Error(), "not a directory") 38 } 39 40 func joinTrailingSep(pathElements ...string) string { 41 joined := filepath.Join(pathElements...) 42 43 return fmt.Sprintf("%s%c", joined, filepath.Separator) 44 } 45 46 func fileContentsEqual(t *testing.T, filenameA, filenameB string) (err error) { 47 t.Logf("checking for equal file contents: %q and %q\n", filenameA, filenameB) 48 49 fileA, err := os.Open(filenameA) 50 if err != nil { 51 return 52 } 53 defer fileA.Close() 54 55 fileB, err := os.Open(filenameB) 56 if err != nil { 57 return 58 } 59 defer fileB.Close() 60 61 hasher := sha256.New() 62 63 if _, err = io.Copy(hasher, fileA); err != nil { 64 return 65 } 66 67 hashA := hasher.Sum(nil) 68 hasher.Reset() 69 70 if _, err = io.Copy(hasher, fileB); err != nil { 71 return 72 } 73 74 hashB := hasher.Sum(nil) 75 76 if !bytes.Equal(hashA, hashB) { 77 err = fmt.Errorf("file content hashes not equal - expected %s, got %s", hex.EncodeToString(hashA), hex.EncodeToString(hashB)) 78 } 79 80 return 81 } 82 83 func dirContentsEqual(t *testing.T, newDir, oldDir string) (err error) { 84 t.Logf("checking for equal directory contents: %q and %q\n", newDir, oldDir) 85 86 var changes []Change 87 88 if changes, err = ChangesDirs(newDir, oldDir); err != nil { 89 return 90 } 91 92 if len(changes) != 0 { 93 err = fmt.Errorf("expected no changes between directories, but got: %v", changes) 94 } 95 96 return 97 } 98 99 func logDirContents(t *testing.T, dirPath string) { 100 logWalkedPaths := filepath.WalkFunc(func(path string, info os.FileInfo, err error) error { 101 if err != nil { 102 t.Errorf("stat error for path %q: %s", path, err) 103 return nil 104 } 105 106 if info.IsDir() { 107 path = joinTrailingSep(path) 108 } 109 110 t.Logf("\t%s", path) 111 112 return nil 113 }) 114 115 t.Logf("logging directory contents: %q", dirPath) 116 117 if err := filepath.Walk(dirPath, logWalkedPaths); err != nil { 118 t.Fatal(err) 119 } 120 } 121 122 func testCopyHelper(t *testing.T, srcPath, dstPath string) (err error) { 123 t.Logf("copying from %q to %q", srcPath, dstPath) 124 125 return CopyResource(srcPath, dstPath) 126 } 127 128 // Basic assumptions about SRC and DST: 129 // 1. SRC must exist. 130 // 2. If SRC ends with a trailing separator, it must be a directory. 131 // 3. DST parent directory must exist. 132 // 4. If DST exists as a file, it must not end with a trailing separator. 133 134 // First get these easy error cases out of the way. 135 136 // Test for error when SRC does not exist. 137 func TestCopyErrSrcNotExists(t *testing.T) { 138 tmpDirA, tmpDirB := getTestTempDirs(t) 139 defer removeAllPaths(tmpDirA, tmpDirB) 140 141 if _, err := CopyInfoSourcePath(filepath.Join(tmpDirA, "file1")); !os.IsNotExist(err) { 142 t.Fatalf("expected IsNotExist error, but got %T: %s", err, err) 143 } 144 } 145 146 // Test for error when SRC ends in a trailing 147 // path separator but it exists as a file. 148 func TestCopyErrSrcNotDir(t *testing.T) { 149 tmpDirA, tmpDirB := getTestTempDirs(t) 150 defer removeAllPaths(tmpDirA, tmpDirB) 151 152 // Load A with some sample files and directories. 153 createSampleDir(t, tmpDirA) 154 155 if _, err := CopyInfoSourcePath(joinTrailingSep(tmpDirA, "file1")); !isNotDir(err) { 156 t.Fatalf("expected IsNotDir error, but got %T: %s", err, err) 157 } 158 } 159 160 // Test for error when SRC is a valid file or directory, 161 // but the DST parent directory does not exist. 162 func TestCopyErrDstParentNotExists(t *testing.T) { 163 tmpDirA, tmpDirB := getTestTempDirs(t) 164 defer removeAllPaths(tmpDirA, tmpDirB) 165 166 // Load A with some sample files and directories. 167 createSampleDir(t, tmpDirA) 168 169 srcInfo := CopyInfo{Path: filepath.Join(tmpDirA, "file1"), Exists: true, IsDir: false} 170 171 // Try with a file source. 172 content, err := TarResource(srcInfo) 173 if err != nil { 174 t.Fatalf("unexpected error %T: %s", err, err) 175 } 176 defer content.Close() 177 178 // Copy to a file whose parent does not exist. 179 if err = CopyTo(content, srcInfo, filepath.Join(tmpDirB, "fakeParentDir", "file1")); err == nil { 180 t.Fatal("expected IsNotExist error, but got nil instead") 181 } 182 183 if !os.IsNotExist(err) { 184 t.Fatalf("expected IsNotExist error, but got %T: %s", err, err) 185 } 186 187 // Try with a directory source. 188 srcInfo = CopyInfo{Path: filepath.Join(tmpDirA, "dir1"), Exists: true, IsDir: true} 189 190 content, err = TarResource(srcInfo) 191 if err != nil { 192 t.Fatalf("unexpected error %T: %s", err, err) 193 } 194 defer content.Close() 195 196 // Copy to a directory whose parent does not exist. 197 if err = CopyTo(content, srcInfo, joinTrailingSep(tmpDirB, "fakeParentDir", "fakeDstDir")); err == nil { 198 t.Fatal("expected IsNotExist error, but got nil instead") 199 } 200 201 if !os.IsNotExist(err) { 202 t.Fatalf("expected IsNotExist error, but got %T: %s", err, err) 203 } 204 } 205 206 // Test for error when DST ends in a trailing 207 // path separator but exists as a file. 208 func TestCopyErrDstNotDir(t *testing.T) { 209 tmpDirA, tmpDirB := getTestTempDirs(t) 210 defer removeAllPaths(tmpDirA, tmpDirB) 211 212 // Load A and B with some sample files and directories. 213 createSampleDir(t, tmpDirA) 214 createSampleDir(t, tmpDirB) 215 216 // Try with a file source. 217 srcInfo := CopyInfo{Path: filepath.Join(tmpDirA, "file1"), Exists: true, IsDir: false} 218 219 content, err := TarResource(srcInfo) 220 if err != nil { 221 t.Fatalf("unexpected error %T: %s", err, err) 222 } 223 defer content.Close() 224 225 if err = CopyTo(content, srcInfo, joinTrailingSep(tmpDirB, "file1")); err == nil { 226 t.Fatal("expected IsNotDir error, but got nil instead") 227 } 228 229 if !isNotDir(err) { 230 t.Fatalf("expected IsNotDir error, but got %T: %s", err, err) 231 } 232 233 // Try with a directory source. 234 srcInfo = CopyInfo{Path: filepath.Join(tmpDirA, "dir1"), Exists: true, IsDir: true} 235 236 content, err = TarResource(srcInfo) 237 if err != nil { 238 t.Fatalf("unexpected error %T: %s", err, err) 239 } 240 defer content.Close() 241 242 if err = CopyTo(content, srcInfo, joinTrailingSep(tmpDirB, "file1")); err == nil { 243 t.Fatal("expected IsNotDir error, but got nil instead") 244 } 245 246 if !isNotDir(err) { 247 t.Fatalf("expected IsNotDir error, but got %T: %s", err, err) 248 } 249 } 250 251 // Possibilities are reduced to the remaining 10 cases: 252 // 253 // case | srcIsDir | onlyDirContents | dstExists | dstIsDir | dstTrSep | action 254 // =================================================================================================== 255 // A | no | - | no | - | no | create file 256 // B | no | - | no | - | yes | error 257 // C | no | - | yes | no | - | overwrite file 258 // D | no | - | yes | yes | - | create file in dst dir 259 // E | yes | no | no | - | - | create dir, copy contents 260 // F | yes | no | yes | no | - | error 261 // G | yes | no | yes | yes | - | copy dir and contents 262 // H | yes | yes | no | - | - | create dir, copy contents 263 // I | yes | yes | yes | no | - | error 264 // J | yes | yes | yes | yes | - | copy dir contents 265 // 266 267 // A. SRC specifies a file and DST (no trailing path separator) doesn't 268 // exist. This should create a file with the name DST and copy the 269 // contents of the source file into it. 270 func TestCopyCaseA(t *testing.T) { 271 tmpDirA, tmpDirB := getTestTempDirs(t) 272 defer removeAllPaths(tmpDirA, tmpDirB) 273 274 // Load A with some sample files and directories. 275 createSampleDir(t, tmpDirA) 276 277 srcPath := filepath.Join(tmpDirA, "file1") 278 dstPath := filepath.Join(tmpDirB, "itWorks.txt") 279 280 var err error 281 282 if err = testCopyHelper(t, srcPath, dstPath); err != nil { 283 t.Fatalf("unexpected error %T: %s", err, err) 284 } 285 286 if err = fileContentsEqual(t, srcPath, dstPath); err != nil { 287 t.Fatal(err) 288 } 289 } 290 291 // B. SRC specifies a file and DST (with trailing path separator) doesn't 292 // exist. This should cause an error because the copy operation cannot 293 // create a directory when copying a single file. 294 func TestCopyCaseB(t *testing.T) { 295 tmpDirA, tmpDirB := getTestTempDirs(t) 296 defer removeAllPaths(tmpDirA, tmpDirB) 297 298 // Load A with some sample files and directories. 299 createSampleDir(t, tmpDirA) 300 301 srcPath := filepath.Join(tmpDirA, "file1") 302 dstDir := joinTrailingSep(tmpDirB, "testDir") 303 304 var err error 305 306 if err = testCopyHelper(t, srcPath, dstDir); err == nil { 307 t.Fatal("expected ErrDirNotExists error, but got nil instead") 308 } 309 310 if err != ErrDirNotExists { 311 t.Fatalf("expected ErrDirNotExists error, but got %T: %s", err, err) 312 } 313 } 314 315 // C. SRC specifies a file and DST exists as a file. This should overwrite 316 // the file at DST with the contents of the source file. 317 func TestCopyCaseC(t *testing.T) { 318 tmpDirA, tmpDirB := getTestTempDirs(t) 319 defer removeAllPaths(tmpDirA, tmpDirB) 320 321 // Load A and B with some sample files and directories. 322 createSampleDir(t, tmpDirA) 323 createSampleDir(t, tmpDirB) 324 325 srcPath := filepath.Join(tmpDirA, "file1") 326 dstPath := filepath.Join(tmpDirB, "file2") 327 328 var err error 329 330 // Ensure they start out different. 331 if err = fileContentsEqual(t, srcPath, dstPath); err == nil { 332 t.Fatal("expected different file contents") 333 } 334 335 if err = testCopyHelper(t, srcPath, dstPath); err != nil { 336 t.Fatalf("unexpected error %T: %s", err, err) 337 } 338 339 if err = fileContentsEqual(t, srcPath, dstPath); err != nil { 340 t.Fatal(err) 341 } 342 } 343 344 // D. SRC specifies a file and DST exists as a directory. This should place 345 // a copy of the source file inside it using the basename from SRC. Ensure 346 // this works whether DST has a trailing path separator or not. 347 func TestCopyCaseD(t *testing.T) { 348 tmpDirA, tmpDirB := getTestTempDirs(t) 349 defer removeAllPaths(tmpDirA, tmpDirB) 350 351 // Load A and B with some sample files and directories. 352 createSampleDir(t, tmpDirA) 353 createSampleDir(t, tmpDirB) 354 355 srcPath := filepath.Join(tmpDirA, "file1") 356 dstDir := filepath.Join(tmpDirB, "dir1") 357 dstPath := filepath.Join(dstDir, "file1") 358 359 var err error 360 361 // Ensure that dstPath doesn't exist. 362 if _, err = os.Stat(dstPath); !os.IsNotExist(err) { 363 t.Fatalf("did not expect dstPath %q to exist", dstPath) 364 } 365 366 if err = testCopyHelper(t, srcPath, dstDir); err != nil { 367 t.Fatalf("unexpected error %T: %s", err, err) 368 } 369 370 if err = fileContentsEqual(t, srcPath, dstPath); err != nil { 371 t.Fatal(err) 372 } 373 374 // Now try again but using a trailing path separator for dstDir. 375 376 if err = os.RemoveAll(dstDir); err != nil { 377 t.Fatalf("unable to remove dstDir: %s", err) 378 } 379 380 if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil { 381 t.Fatalf("unable to make dstDir: %s", err) 382 } 383 384 dstDir = joinTrailingSep(tmpDirB, "dir1") 385 386 if err = testCopyHelper(t, srcPath, dstDir); err != nil { 387 t.Fatalf("unexpected error %T: %s", err, err) 388 } 389 390 if err = fileContentsEqual(t, srcPath, dstPath); err != nil { 391 t.Fatal(err) 392 } 393 } 394 395 // E. SRC specifies a directory and DST does not exist. This should create a 396 // directory at DST and copy the contents of the SRC directory into the DST 397 // directory. Ensure this works whether DST has a trailing path separator or 398 // not. 399 func TestCopyCaseE(t *testing.T) { 400 tmpDirA, tmpDirB := getTestTempDirs(t) 401 defer removeAllPaths(tmpDirA, tmpDirB) 402 403 // Load A with some sample files and directories. 404 createSampleDir(t, tmpDirA) 405 406 srcDir := filepath.Join(tmpDirA, "dir1") 407 dstDir := filepath.Join(tmpDirB, "testDir") 408 409 var err error 410 411 if err = testCopyHelper(t, srcDir, dstDir); err != nil { 412 t.Fatalf("unexpected error %T: %s", err, err) 413 } 414 415 if err = dirContentsEqual(t, dstDir, srcDir); err != nil { 416 t.Log("dir contents not equal") 417 logDirContents(t, tmpDirA) 418 logDirContents(t, tmpDirB) 419 t.Fatal(err) 420 } 421 422 // Now try again but using a trailing path separator for dstDir. 423 424 if err = os.RemoveAll(dstDir); err != nil { 425 t.Fatalf("unable to remove dstDir: %s", err) 426 } 427 428 dstDir = joinTrailingSep(tmpDirB, "testDir") 429 430 if err = testCopyHelper(t, srcDir, dstDir); err != nil { 431 t.Fatalf("unexpected error %T: %s", err, err) 432 } 433 434 if err = dirContentsEqual(t, dstDir, srcDir); err != nil { 435 t.Fatal(err) 436 } 437 } 438 439 // F. SRC specifies a directory and DST exists as a file. This should cause an 440 // error as it is not possible to overwrite a file with a directory. 441 func TestCopyCaseF(t *testing.T) { 442 tmpDirA, tmpDirB := getTestTempDirs(t) 443 defer removeAllPaths(tmpDirA, tmpDirB) 444 445 // Load A and B with some sample files and directories. 446 createSampleDir(t, tmpDirA) 447 createSampleDir(t, tmpDirB) 448 449 srcDir := filepath.Join(tmpDirA, "dir1") 450 dstFile := filepath.Join(tmpDirB, "file1") 451 452 var err error 453 454 if err = testCopyHelper(t, srcDir, dstFile); err == nil { 455 t.Fatal("expected ErrCannotCopyDir error, but got nil instead") 456 } 457 458 if err != ErrCannotCopyDir { 459 t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err) 460 } 461 } 462 463 // G. SRC specifies a directory and DST exists as a directory. This should copy 464 // the SRC directory and all its contents to the DST directory. Ensure this 465 // works whether DST has a trailing path separator or not. 466 func TestCopyCaseG(t *testing.T) { 467 tmpDirA, tmpDirB := getTestTempDirs(t) 468 defer removeAllPaths(tmpDirA, tmpDirB) 469 470 // Load A and B with some sample files and directories. 471 createSampleDir(t, tmpDirA) 472 createSampleDir(t, tmpDirB) 473 474 srcDir := filepath.Join(tmpDirA, "dir1") 475 dstDir := filepath.Join(tmpDirB, "dir2") 476 resultDir := filepath.Join(dstDir, "dir1") 477 478 var err error 479 480 if err = testCopyHelper(t, srcDir, dstDir); err != nil { 481 t.Fatalf("unexpected error %T: %s", err, err) 482 } 483 484 if err = dirContentsEqual(t, resultDir, srcDir); err != nil { 485 t.Fatal(err) 486 } 487 488 // Now try again but using a trailing path separator for dstDir. 489 490 if err = os.RemoveAll(dstDir); err != nil { 491 t.Fatalf("unable to remove dstDir: %s", err) 492 } 493 494 if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil { 495 t.Fatalf("unable to make dstDir: %s", err) 496 } 497 498 dstDir = joinTrailingSep(tmpDirB, "dir2") 499 500 if err = testCopyHelper(t, srcDir, dstDir); err != nil { 501 t.Fatalf("unexpected error %T: %s", err, err) 502 } 503 504 if err = dirContentsEqual(t, resultDir, srcDir); err != nil { 505 t.Fatal(err) 506 } 507 } 508 509 // H. SRC specifies a directory's contents only and DST does not exist. This 510 // should create a directory at DST and copy the contents of the SRC 511 // directory (but not the directory itself) into the DST directory. Ensure 512 // this works whether DST has a trailing path separator or not. 513 func TestCopyCaseH(t *testing.T) { 514 tmpDirA, tmpDirB := getTestTempDirs(t) 515 defer removeAllPaths(tmpDirA, tmpDirB) 516 517 // Load A with some sample files and directories. 518 createSampleDir(t, tmpDirA) 519 520 srcDir := joinTrailingSep(tmpDirA, "dir1") + "." 521 dstDir := filepath.Join(tmpDirB, "testDir") 522 523 var err error 524 525 if err = testCopyHelper(t, srcDir, dstDir); err != nil { 526 t.Fatalf("unexpected error %T: %s", err, err) 527 } 528 529 if err = dirContentsEqual(t, dstDir, srcDir); err != nil { 530 t.Log("dir contents not equal") 531 logDirContents(t, tmpDirA) 532 logDirContents(t, tmpDirB) 533 t.Fatal(err) 534 } 535 536 // Now try again but using a trailing path separator for dstDir. 537 538 if err = os.RemoveAll(dstDir); err != nil { 539 t.Fatalf("unable to remove dstDir: %s", err) 540 } 541 542 dstDir = joinTrailingSep(tmpDirB, "testDir") 543 544 if err = testCopyHelper(t, srcDir, dstDir); err != nil { 545 t.Fatalf("unexpected error %T: %s", err, err) 546 } 547 548 if err = dirContentsEqual(t, dstDir, srcDir); err != nil { 549 t.Log("dir contents not equal") 550 logDirContents(t, tmpDirA) 551 logDirContents(t, tmpDirB) 552 t.Fatal(err) 553 } 554 } 555 556 // I. SRC specifies a directory's contents only and DST exists as a file. This 557 // should cause an error as it is not possible to overwrite a file with a 558 // directory. 559 func TestCopyCaseI(t *testing.T) { 560 tmpDirA, tmpDirB := getTestTempDirs(t) 561 defer removeAllPaths(tmpDirA, tmpDirB) 562 563 // Load A and B with some sample files and directories. 564 createSampleDir(t, tmpDirA) 565 createSampleDir(t, tmpDirB) 566 567 srcDir := joinTrailingSep(tmpDirA, "dir1") + "." 568 dstFile := filepath.Join(tmpDirB, "file1") 569 570 var err error 571 572 if err = testCopyHelper(t, srcDir, dstFile); err == nil { 573 t.Fatal("expected ErrCannotCopyDir error, but got nil instead") 574 } 575 576 if err != ErrCannotCopyDir { 577 t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err) 578 } 579 } 580 581 // J. SRC specifies a directory's contents only and DST exists as a directory. 582 // This should copy the contents of the SRC directory (but not the directory 583 // itself) into the DST directory. Ensure this works whether DST has a 584 // trailing path separator or not. 585 func TestCopyCaseJ(t *testing.T) { 586 tmpDirA, tmpDirB := getTestTempDirs(t) 587 defer removeAllPaths(tmpDirA, tmpDirB) 588 589 // Load A and B with some sample files and directories. 590 createSampleDir(t, tmpDirA) 591 createSampleDir(t, tmpDirB) 592 593 srcDir := joinTrailingSep(tmpDirA, "dir1") + "." 594 dstDir := filepath.Join(tmpDirB, "dir5") 595 596 var err error 597 598 if err = testCopyHelper(t, srcDir, dstDir); err != nil { 599 t.Fatalf("unexpected error %T: %s", err, err) 600 } 601 602 if err = dirContentsEqual(t, dstDir, srcDir); err != nil { 603 t.Fatal(err) 604 } 605 606 // Now try again but using a trailing path separator for dstDir. 607 608 if err = os.RemoveAll(dstDir); err != nil { 609 t.Fatalf("unable to remove dstDir: %s", err) 610 } 611 612 if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil { 613 t.Fatalf("unable to make dstDir: %s", err) 614 } 615 616 dstDir = joinTrailingSep(tmpDirB, "dir5") 617 618 if err = testCopyHelper(t, srcDir, dstDir); err != nil { 619 t.Fatalf("unexpected error %T: %s", err, err) 620 } 621 622 if err = dirContentsEqual(t, dstDir, srcDir); err != nil { 623 t.Fatal(err) 624 } 625 }