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  }