github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/pkg/archive/changes_test.go (about)

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