archive/changes_test.go
f7238f94
 package archive
 
 import (
 	"io/ioutil"
 	"os"
 	"os/exec"
 	"path"
 	"sort"
 	"testing"
 	"time"
 )
 
 func max(x, y int) int {
 	if x >= y {
 		return x
 	}
 	return y
 }
 
 func copyDir(src, dst string) error {
 	cmd := exec.Command("cp", "-a", src, dst)
 	if err := cmd.Run(); err != nil {
 		return err
 	}
 	return nil
 }
 
 // Helper to sort []Change by path
 type byPath struct{ changes []Change }
 
 func (b byPath) Less(i, j int) bool { return b.changes[i].Path < b.changes[j].Path }
 func (b byPath) Len() int           { return len(b.changes) }
 func (b byPath) Swap(i, j int)      { b.changes[i], b.changes[j] = b.changes[j], b.changes[i] }
 
 type FileType uint32
 
 const (
 	Regular FileType = iota
 	Dir
 	Symlink
 )
 
 type FileData struct {
 	filetype    FileType
 	path        string
 	contents    string
 	permissions os.FileMode
 }
 
 func createSampleDir(t *testing.T, root string) {
 	files := []FileData{
 		{Regular, "file1", "file1\n", 0600},
 		{Regular, "file2", "file2\n", 0666},
 		{Regular, "file3", "file3\n", 0404},
 		{Regular, "file4", "file4\n", 0600},
 		{Regular, "file5", "file5\n", 0600},
 		{Regular, "file6", "file6\n", 0600},
 		{Regular, "file7", "file7\n", 0600},
 		{Dir, "dir1", "", 0740},
 		{Regular, "dir1/file1-1", "file1-1\n", 01444},
 		{Regular, "dir1/file1-2", "file1-2\n", 0666},
 		{Dir, "dir2", "", 0700},
 		{Regular, "dir2/file2-1", "file2-1\n", 0666},
 		{Regular, "dir2/file2-2", "file2-2\n", 0666},
 		{Dir, "dir3", "", 0700},
 		{Regular, "dir3/file3-1", "file3-1\n", 0666},
 		{Regular, "dir3/file3-2", "file3-2\n", 0666},
 		{Dir, "dir4", "", 0700},
 		{Regular, "dir4/file3-1", "file4-1\n", 0666},
 		{Regular, "dir4/file3-2", "file4-2\n", 0666},
 		{Symlink, "symlink1", "target1", 0666},
 		{Symlink, "symlink2", "target2", 0666},
 	}
0d1b5d76
 
 	now := time.Now()
f7238f94
 	for _, info := range files {
0d1b5d76
 		p := path.Join(root, info.path)
f7238f94
 		if info.filetype == Dir {
0d1b5d76
 			if err := os.MkdirAll(p, info.permissions); err != nil {
f7238f94
 				t.Fatal(err)
 			}
 		} else if info.filetype == Regular {
0d1b5d76
 			if err := ioutil.WriteFile(p, []byte(info.contents), info.permissions); err != nil {
f7238f94
 				t.Fatal(err)
 			}
 		} else if info.filetype == Symlink {
0d1b5d76
 			if err := os.Symlink(info.contents, p); err != nil {
 				t.Fatal(err)
 			}
 		}
 
 		if info.filetype != Symlink {
 			// Set a consistent ctime, atime for all files and dirs
 			if err := os.Chtimes(p, now, now); err != nil {
f7238f94
 				t.Fatal(err)
 			}
 		}
 	}
 }
 
 // Create an directory, copy it, make sure we report no changes between the two
 func TestChangesDirsEmpty(t *testing.T) {
 	src, err := ioutil.TempDir("", "docker-changes-test")
 	if err != nil {
 		t.Fatal(err)
 	}
 	createSampleDir(t, src)
 	dst := src + "-copy"
 	if err := copyDir(src, dst); err != nil {
 		t.Fatal(err)
 	}
 	changes, err := ChangesDirs(dst, src)
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	if len(changes) != 0 {
 		t.Fatalf("Reported changes for identical dirs: %v", changes)
 	}
 	os.RemoveAll(src)
 	os.RemoveAll(dst)
 }
 
 func mutateSampleDir(t *testing.T, root string) {
 	// Remove a regular file
 	if err := os.RemoveAll(path.Join(root, "file1")); err != nil {
 		t.Fatal(err)
 	}
 
 	// Remove a directory
 	if err := os.RemoveAll(path.Join(root, "dir1")); err != nil {
 		t.Fatal(err)
 	}
 
 	// Remove a symlink
 	if err := os.RemoveAll(path.Join(root, "symlink1")); err != nil {
 		t.Fatal(err)
 	}
 
 	// Rewrite a file
794b5de7
 	if err := ioutil.WriteFile(path.Join(root, "file2"), []byte("fileNN\n"), 0777); err != nil {
f7238f94
 		t.Fatal(err)
 	}
 
 	// Replace a file
 	if err := os.RemoveAll(path.Join(root, "file3")); err != nil {
 		t.Fatal(err)
 	}
794b5de7
 	if err := ioutil.WriteFile(path.Join(root, "file3"), []byte("fileMM\n"), 0404); err != nil {
f7238f94
 		t.Fatal(err)
 	}
 
 	// Touch file
794b5de7
 	if err := os.Chtimes(path.Join(root, "file4"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil {
f7238f94
 		t.Fatal(err)
 	}
 
 	// Replace file with dir
 	if err := os.RemoveAll(path.Join(root, "file5")); err != nil {
 		t.Fatal(err)
 	}
 	if err := os.MkdirAll(path.Join(root, "file5"), 0666); err != nil {
 		t.Fatal(err)
 	}
 
 	// Create new file
 	if err := ioutil.WriteFile(path.Join(root, "filenew"), []byte("filenew\n"), 0777); err != nil {
 		t.Fatal(err)
 	}
 
 	// Create new dir
 	if err := os.MkdirAll(path.Join(root, "dirnew"), 0766); err != nil {
 		t.Fatal(err)
 	}
 
 	// Create a new symlink
 	if err := os.Symlink("targetnew", path.Join(root, "symlinknew")); err != nil {
 		t.Fatal(err)
 	}
 
 	// Change a symlink
 	if err := os.RemoveAll(path.Join(root, "symlink2")); err != nil {
 		t.Fatal(err)
 	}
 	if err := os.Symlink("target2change", path.Join(root, "symlink2")); err != nil {
 		t.Fatal(err)
 	}
 
 	// Replace dir with file
 	if err := os.RemoveAll(path.Join(root, "dir2")); err != nil {
 		t.Fatal(err)
 	}
 	if err := ioutil.WriteFile(path.Join(root, "dir2"), []byte("dir2\n"), 0777); err != nil {
 		t.Fatal(err)
 	}
 
 	// Touch dir
794b5de7
 	if err := os.Chtimes(path.Join(root, "dir3"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil {
f7238f94
 		t.Fatal(err)
 	}
 }
 
 func TestChangesDirsMutated(t *testing.T) {
 	src, err := ioutil.TempDir("", "docker-changes-test")
 	if err != nil {
 		t.Fatal(err)
 	}
 	createSampleDir(t, src)
 	dst := src + "-copy"
 	if err := copyDir(src, dst); err != nil {
 		t.Fatal(err)
 	}
0d1b5d76
 	defer os.RemoveAll(src)
 	defer os.RemoveAll(dst)
 
f7238f94
 	mutateSampleDir(t, dst)
 
 	changes, err := ChangesDirs(dst, src)
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	sort.Sort(byPath{changes})
 
 	expectedChanges := []Change{
 		{"/dir1", ChangeDelete},
 		{"/dir2", ChangeModify},
 		{"/dir3", ChangeModify},
 		{"/dirnew", ChangeAdd},
 		{"/file1", ChangeDelete},
 		{"/file2", ChangeModify},
 		{"/file3", ChangeModify},
 		{"/file4", ChangeModify},
 		{"/file5", ChangeModify},
 		{"/filenew", ChangeAdd},
 		{"/symlink1", ChangeDelete},
 		{"/symlink2", ChangeModify},
 		{"/symlinknew", ChangeAdd},
 	}
 
0d1b5d76
 	for i := 0; i < max(len(changes), len(expectedChanges)); i++ {
f7238f94
 		if i >= len(expectedChanges) {
 			t.Fatalf("unexpected change %s\n", changes[i].String())
 		}
 		if i >= len(changes) {
 			t.Fatalf("no change for expected change %s\n", expectedChanges[i].String())
 		}
 		if changes[i].Path == expectedChanges[i].Path {
 			if changes[i] != expectedChanges[i] {
a6c9a332
 				t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String())
f7238f94
 			}
 		} else if changes[i].Path < expectedChanges[i].Path {
 			t.Fatalf("unexpected change %s\n", changes[i].String())
 		} else {
0d1b5d76
 			t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String())
f7238f94
 		}
 	}
 }
 
 func TestApplyLayer(t *testing.T) {
a8af12f8
 	src, err := ioutil.TempDir("", "docker-changes-test")
 	if err != nil {
 		t.Fatal(err)
 	}
 	createSampleDir(t, src)
 	defer os.RemoveAll(src)
 	dst := src + "-copy"
 	if err := copyDir(src, dst); err != nil {
 		t.Fatal(err)
 	}
 	mutateSampleDir(t, dst)
 	defer os.RemoveAll(dst)
 
 	changes, err := ChangesDirs(dst, src)
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	layer, err := ExportChanges(dst, changes)
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	layerCopy, err := NewTempArchive(layer, "")
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	if err := ApplyLayer(src, layerCopy); err != nil {
 		t.Fatal(err)
 	}
 
 	changes2, err := ChangesDirs(src, dst)
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	if len(changes2) != 0 {
0fccf0f6
 		t.Fatalf("Unexpected differences after reapplying mutation: %v", changes2)
a8af12f8
 	}
f7238f94
 }