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  }