Selaa lähdekoodia

Expand graphtest package

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
Derek McGowan 9 vuotta sitten
vanhempi
commit
8b0441d42c

+ 264 - 0
daemon/graphdriver/graphtest/graphbench_unix.go

@@ -0,0 +1,264 @@
+// +build linux freebsd
+
+package graphtest
+
+import (
+	"bytes"
+	"io"
+	"io/ioutil"
+	"path/filepath"
+	"testing"
+
+	"github.com/docker/docker/pkg/stringid"
+)
+
+// DriverBenchExists benchmarks calls to exist
+func DriverBenchExists(b *testing.B, drivername string, driveroptions ...string) {
+	driver := GetDriver(b, drivername, driveroptions...)
+	defer PutDriver(b)
+
+	base := stringid.GenerateRandomID()
+
+	if err := driver.Create(base, "", "", nil); err != nil {
+		b.Fatal(err)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		if !driver.Exists(base) {
+			b.Fatal("Newly created image doesn't exist")
+		}
+	}
+}
+
+// DriverBenchGetEmpty benchmarks calls to get on an empty layer
+func DriverBenchGetEmpty(b *testing.B, drivername string, driveroptions ...string) {
+	driver := GetDriver(b, drivername, driveroptions...)
+	defer PutDriver(b)
+
+	base := stringid.GenerateRandomID()
+
+	if err := driver.Create(base, "", "", nil); err != nil {
+		b.Fatal(err)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_, err := driver.Get(base, "")
+		b.StopTimer()
+		if err != nil {
+			b.Fatalf("Error getting mount: %s", err)
+		}
+		if err := driver.Put(base); err != nil {
+			b.Fatalf("Error putting mount: %s", err)
+		}
+		b.StartTimer()
+	}
+}
+
+// DriverBenchDiffBase benchmarks calls to diff on a root layer
+func DriverBenchDiffBase(b *testing.B, drivername string, driveroptions ...string) {
+	driver := GetDriver(b, drivername, driveroptions...)
+	defer PutDriver(b)
+
+	base := stringid.GenerateRandomID()
+
+	if err := driver.Create(base, "", "", nil); err != nil {
+		b.Fatal(err)
+	}
+
+	if err := addFiles(driver, base, 3); err != nil {
+		b.Fatal(err)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		arch, err := driver.Diff(base, "")
+		if err != nil {
+			b.Fatal(err)
+		}
+		_, err = io.Copy(ioutil.Discard, arch)
+		if err != nil {
+			b.Fatalf("Error copying archive: %s", err)
+		}
+		arch.Close()
+	}
+}
+
+// DriverBenchDiffN benchmarks calls to diff on two layers with
+// a provided number of files on the lower and upper layers.
+func DriverBenchDiffN(b *testing.B, bottom, top int, drivername string, driveroptions ...string) {
+	driver := GetDriver(b, drivername, driveroptions...)
+	defer PutDriver(b)
+	base := stringid.GenerateRandomID()
+	upper := stringid.GenerateRandomID()
+
+	if err := driver.Create(base, "", "", nil); err != nil {
+		b.Fatal(err)
+	}
+
+	if err := addManyFiles(driver, base, bottom, 3); err != nil {
+		b.Fatal(err)
+	}
+
+	if err := driver.Create(upper, base, "", nil); err != nil {
+		b.Fatal(err)
+	}
+
+	if err := addManyFiles(driver, upper, top, 6); err != nil {
+		b.Fatal(err)
+	}
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		arch, err := driver.Diff(upper, "")
+		if err != nil {
+			b.Fatal(err)
+		}
+		_, err = io.Copy(ioutil.Discard, arch)
+		if err != nil {
+			b.Fatalf("Error copying archive: %s", err)
+		}
+		arch.Close()
+	}
+}
+
+// DriverBenchDiffApplyN benchmarks calls to diff and apply together
+func DriverBenchDiffApplyN(b *testing.B, fileCount int, drivername string, driveroptions ...string) {
+	driver := GetDriver(b, drivername, driveroptions...)
+	defer PutDriver(b)
+	base := stringid.GenerateRandomID()
+	upper := stringid.GenerateRandomID()
+
+	if err := driver.Create(base, "", "", nil); err != nil {
+		b.Fatal(err)
+	}
+
+	if err := addManyFiles(driver, base, fileCount, 3); err != nil {
+		b.Fatal(err)
+	}
+
+	if err := driver.Create(upper, base, "", nil); err != nil {
+		b.Fatal(err)
+	}
+
+	if err := addManyFiles(driver, upper, fileCount, 6); err != nil {
+		b.Fatal(err)
+	}
+	diffSize, err := driver.DiffSize(upper, "")
+	if err != nil {
+		b.Fatal(err)
+	}
+	b.ResetTimer()
+	b.StopTimer()
+	for i := 0; i < b.N; i++ {
+		diff := stringid.GenerateRandomID()
+		if err := driver.Create(diff, base, "", nil); err != nil {
+			b.Fatal(err)
+		}
+
+		if err := checkManyFiles(driver, diff, fileCount, 3); err != nil {
+			b.Fatal(err)
+		}
+
+		b.StartTimer()
+
+		arch, err := driver.Diff(upper, "")
+		if err != nil {
+			b.Fatal(err)
+		}
+
+		applyDiffSize, err := driver.ApplyDiff(diff, "", arch)
+		if err != nil {
+			b.Fatal(err)
+		}
+
+		b.StopTimer()
+		arch.Close()
+
+		if applyDiffSize != diffSize {
+			// TODO: enforce this
+			//b.Fatalf("Apply diff size different, got %d, expected %s", applyDiffSize, diffSize)
+		}
+		if err := checkManyFiles(driver, diff, fileCount, 6); err != nil {
+			b.Fatal(err)
+		}
+	}
+}
+
+// DriverBenchDeepLayerDiff benchmarks calls to diff on top of a given number of layers.
+func DriverBenchDeepLayerDiff(b *testing.B, layerCount int, drivername string, driveroptions ...string) {
+	driver := GetDriver(b, drivername, driveroptions...)
+	defer PutDriver(b)
+
+	base := stringid.GenerateRandomID()
+
+	if err := driver.Create(base, "", "", nil); err != nil {
+		b.Fatal(err)
+	}
+
+	if err := addFiles(driver, base, 50); err != nil {
+		b.Fatal(err)
+	}
+
+	topLayer, err := addManyLayers(driver, base, layerCount)
+	if err != nil {
+		b.Fatal(err)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		arch, err := driver.Diff(topLayer, "")
+		if err != nil {
+			b.Fatal(err)
+		}
+		_, err = io.Copy(ioutil.Discard, arch)
+		if err != nil {
+			b.Fatalf("Error copying archive: %s", err)
+		}
+		arch.Close()
+	}
+}
+
+// DriverBenchDeepLayerRead benchmarks calls to read a file under a given number of layers.
+func DriverBenchDeepLayerRead(b *testing.B, layerCount int, drivername string, driveroptions ...string) {
+	driver := GetDriver(b, drivername, driveroptions...)
+	defer PutDriver(b)
+
+	base := stringid.GenerateRandomID()
+
+	if err := driver.Create(base, "", "", nil); err != nil {
+		b.Fatal(err)
+	}
+
+	content := []byte("test content")
+	if err := addFile(driver, base, "testfile.txt", content); err != nil {
+		b.Fatal(err)
+	}
+
+	topLayer, err := addManyLayers(driver, base, layerCount)
+	if err != nil {
+		b.Fatal(err)
+	}
+
+	root, err := driver.Get(topLayer, "")
+	if err != nil {
+		b.Fatal(err)
+	}
+	defer driver.Put(topLayer)
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+
+		// Read content
+		c, err := ioutil.ReadFile(filepath.Join(root, "testfile.txt"))
+		if err != nil {
+			b.Fatal(err)
+		}
+
+		b.StopTimer()
+		if bytes.Compare(c, content) != 0 {
+			b.Fatalf("Wrong content in file %v, expected %v", c, content)
+		}
+		b.StartTimer()
+	}
+}

+ 134 - 153
daemon/graphdriver/graphtest/graphtest_unix.go

@@ -3,7 +3,7 @@
 package graphtest
 
 import (
-	"fmt"
+	"bytes"
 	"io/ioutil"
 	"math/rand"
 	"os"
@@ -14,6 +14,7 @@ import (
 	"unsafe"
 
 	"github.com/docker/docker/daemon/graphdriver"
+	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/go-units"
 )
 
@@ -30,47 +31,7 @@ type Driver struct {
 	refCount int
 }
 
-// InitLoopbacks ensures that the loopback devices are properly created within
-// the system running the device mapper tests.
-func InitLoopbacks() error {
-	statT, err := getBaseLoopStats()
-	if err != nil {
-		return err
-	}
-	// create at least 8 loopback files, ya, that is a good number
-	for i := 0; i < 8; i++ {
-		loopPath := fmt.Sprintf("/dev/loop%d", i)
-		// only create new loopback files if they don't exist
-		if _, err := os.Stat(loopPath); err != nil {
-			if mkerr := syscall.Mknod(loopPath,
-				uint32(statT.Mode|syscall.S_IFBLK), int((7<<8)|(i&0xff)|((i&0xfff00)<<12))); mkerr != nil {
-				return mkerr
-			}
-			os.Chown(loopPath, int(statT.Uid), int(statT.Gid))
-		}
-	}
-	return nil
-}
-
-// getBaseLoopStats inspects /dev/loop0 to collect uid,gid, and mode for the
-// loop0 device on the system.  If it does not exist we assume 0,0,0660 for the
-// stat data
-func getBaseLoopStats() (*syscall.Stat_t, error) {
-	loop0, err := os.Stat("/dev/loop0")
-	if err != nil {
-		if os.IsNotExist(err) {
-			return &syscall.Stat_t{
-				Uid:  0,
-				Gid:  0,
-				Mode: 0660,
-			}, nil
-		}
-		return nil, err
-	}
-	return loop0.Sys().(*syscall.Stat_t), nil
-}
-
-func newDriver(t *testing.T, name string) *Driver {
+func newDriver(t testing.TB, name string, options []string) *Driver {
 	root, err := ioutil.TempDir("", "docker-graphtest-")
 	if err != nil {
 		t.Fatal(err)
@@ -80,7 +41,7 @@ func newDriver(t *testing.T, name string) *Driver {
 		t.Fatal(err)
 	}
 
-	d, err := graphdriver.GetDriver(name, root, nil, nil, nil)
+	d, err := graphdriver.GetDriver(name, root, options, nil, nil)
 	if err != nil {
 		t.Logf("graphdriver: %v\n", err)
 		if err == graphdriver.ErrNotSupported || err == graphdriver.ErrPrerequisites || err == graphdriver.ErrIncompatibleFS {
@@ -91,7 +52,7 @@ func newDriver(t *testing.T, name string) *Driver {
 	return &Driver{d, root, 1}
 }
 
-func cleanup(t *testing.T, d *Driver) {
+func cleanup(t testing.TB, d *Driver) {
 	if err := drv.Cleanup(); err != nil {
 		t.Fatal(err)
 	}
@@ -99,9 +60,9 @@ func cleanup(t *testing.T, d *Driver) {
 }
 
 // GetDriver create a new driver with given name or return an existing driver with the name updating the reference count.
-func GetDriver(t *testing.T, name string) graphdriver.Driver {
+func GetDriver(t testing.TB, name string, options ...string) graphdriver.Driver {
 	if drv == nil {
-		drv = newDriver(t, name)
+		drv = newDriver(t, name, options)
 	} else {
 		drv.refCount++
 	}
@@ -109,7 +70,7 @@ func GetDriver(t *testing.T, name string) graphdriver.Driver {
 }
 
 // PutDriver removes the driver if it is no longer used and updates the reference count.
-func PutDriver(t *testing.T) {
+func PutDriver(t testing.TB) {
 	if drv == nil {
 		t.Skip("No driver to put!")
 	}
@@ -120,190 +81,210 @@ func PutDriver(t *testing.T) {
 	}
 }
 
-func verifyFile(t *testing.T, path string, mode os.FileMode, uid, gid uint32) {
-	fi, err := os.Stat(path)
-	if err != nil {
+// DriverTestCreateEmpty creates a new image and verifies it is empty and the right metadata
+func DriverTestCreateEmpty(t testing.TB, drivername string, driverOptions ...string) {
+	driver := GetDriver(t, drivername, driverOptions...)
+	defer PutDriver(t)
+
+	if err := driver.Create("empty", "", "", nil); err != nil {
 		t.Fatal(err)
 	}
 
-	if fi.Mode()&os.ModeType != mode&os.ModeType {
-		t.Fatalf("Expected %s type 0x%x, got 0x%x", path, mode&os.ModeType, fi.Mode()&os.ModeType)
-	}
+	defer func() {
+		if err := driver.Remove("empty"); err != nil {
+			t.Fatal(err)
+		}
+	}()
 
-	if fi.Mode()&os.ModePerm != mode&os.ModePerm {
-		t.Fatalf("Expected %s mode %o, got %o", path, mode&os.ModePerm, fi.Mode()&os.ModePerm)
+	if !driver.Exists("empty") {
+		t.Fatal("Newly created image doesn't exist")
 	}
 
-	if fi.Mode()&os.ModeSticky != mode&os.ModeSticky {
-		t.Fatalf("Expected %s sticky 0x%x, got 0x%x", path, mode&os.ModeSticky, fi.Mode()&os.ModeSticky)
+	dir, err := driver.Get("empty", "")
+	if err != nil {
+		t.Fatal(err)
 	}
 
-	if fi.Mode()&os.ModeSetuid != mode&os.ModeSetuid {
-		t.Fatalf("Expected %s setuid 0x%x, got 0x%x", path, mode&os.ModeSetuid, fi.Mode()&os.ModeSetuid)
-	}
+	verifyFile(t, dir, 0755|os.ModeDir, 0, 0)
 
-	if fi.Mode()&os.ModeSetgid != mode&os.ModeSetgid {
-		t.Fatalf("Expected %s setgid 0x%x, got 0x%x", path, mode&os.ModeSetgid, fi.Mode()&os.ModeSetgid)
+	// Verify that the directory is empty
+	fis, err := readDir(dir)
+	if err != nil {
+		t.Fatal(err)
 	}
 
-	if stat, ok := fi.Sys().(*syscall.Stat_t); ok {
-		if stat.Uid != uid {
-			t.Fatalf("%s no owned by uid %d", path, uid)
-		}
-		if stat.Gid != gid {
-			t.Fatalf("%s not owned by gid %d", path, gid)
-		}
+	if len(fis) != 0 {
+		t.Fatal("New directory not empty")
 	}
 
+	driver.Put("empty")
 }
 
-// readDir reads a directory just like ioutil.ReadDir()
-// then hides specific files (currently "lost+found")
-// so the tests don't "see" it
-func readDir(dir string) ([]os.FileInfo, error) {
-	a, err := ioutil.ReadDir(dir)
-	if err != nil {
-		return nil, err
-	}
+// DriverTestCreateBase create a base driver and verify.
+func DriverTestCreateBase(t testing.TB, drivername string, driverOptions ...string) {
+	driver := GetDriver(t, drivername, driverOptions...)
+	defer PutDriver(t)
 
-	b := a[:0]
-	for _, x := range a {
-		if x.Name() != "lost+found" { // ext4 always have this dir
-			b = append(b, x)
+	createBase(t, driver, "Base")
+	defer func() {
+		if err := driver.Remove("Base"); err != nil {
+			t.Fatal(err)
 		}
-	}
-
-	return b, nil
+	}()
+	verifyBase(t, driver, "Base")
 }
 
-// DriverTestCreateEmpty creates a new image and verifies it is empty and the right metadata
-func DriverTestCreateEmpty(t *testing.T, drivername string) {
-	driver := GetDriver(t, drivername)
+// DriverTestCreateSnap Create a driver and snap and verify.
+func DriverTestCreateSnap(t testing.TB, drivername string, driverOptions ...string) {
+	driver := GetDriver(t, drivername, driverOptions...)
 	defer PutDriver(t)
 
-	if err := driver.Create("empty", "", "", nil); err != nil {
+	createBase(t, driver, "Base")
+
+	defer func() {
+		if err := driver.Remove("Base"); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	if err := driver.Create("Snap", "Base", "", nil); err != nil {
 		t.Fatal(err)
 	}
 
 	defer func() {
-		if err := driver.Remove("empty"); err != nil {
+		if err := driver.Remove("Snap"); err != nil {
 			t.Fatal(err)
 		}
 	}()
 
-	if !driver.Exists("empty") {
-		t.Fatal("Newly created image doesn't exist")
-	}
+	verifyBase(t, driver, "Snap")
+}
 
-	dir, err := driver.Get("empty", "")
-	if err != nil {
+// DriverTestDeepLayerRead reads a file from a lower layer under a given number of layers
+func DriverTestDeepLayerRead(t testing.TB, layerCount int, drivername string, driverOptions ...string) {
+	driver := GetDriver(t, drivername, driverOptions...)
+	defer PutDriver(t)
+
+	base := stringid.GenerateRandomID()
+
+	if err := driver.Create(base, "", "", nil); err != nil {
 		t.Fatal(err)
 	}
 
-	verifyFile(t, dir, 0755|os.ModeDir, 0, 0)
+	content := []byte("test content")
+	if err := addFile(driver, base, "testfile.txt", content); err != nil {
+		t.Fatal(err)
+	}
 
-	// Verify that the directory is empty
-	fis, err := readDir(dir)
+	topLayer, err := addManyLayers(driver, base, layerCount)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	if len(fis) != 0 {
-		t.Fatal("New directory not empty")
+	err = checkManyLayers(driver, topLayer, layerCount)
+	if err != nil {
+		t.Fatal(err)
 	}
 
-	driver.Put("empty")
+	if err := checkFile(driver, topLayer, "testfile.txt", content); err != nil {
+		t.Fatal(err)
+	}
 }
 
-func createBase(t *testing.T, driver graphdriver.Driver, name string) {
-	// We need to be able to set any perms
-	oldmask := syscall.Umask(0)
-	defer syscall.Umask(oldmask)
+// DriverTestDiffApply tests diffing and applying produces the same layer
+func DriverTestDiffApply(t testing.TB, fileCount int, drivername string, driverOptions ...string) {
+	driver := GetDriver(t, drivername, driverOptions...)
+	defer PutDriver(t)
+	base := stringid.GenerateRandomID()
+	upper := stringid.GenerateRandomID()
 
-	if err := driver.CreateReadWrite(name, "", "", nil); err != nil {
+	if err := driver.Create(base, "", "", nil); err != nil {
 		t.Fatal(err)
 	}
 
-	dir, err := driver.Get(name, "")
-	if err != nil {
+	if err := addManyFiles(driver, base, fileCount, 3); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := driver.Create(upper, base, "", nil); err != nil {
 		t.Fatal(err)
 	}
-	defer driver.Put(name)
 
-	subdir := path.Join(dir, "a subdir")
-	if err := os.Mkdir(subdir, 0705|os.ModeSticky); err != nil {
+	if err := addManyFiles(driver, upper, fileCount, 6); err != nil {
 		t.Fatal(err)
 	}
-	if err := os.Chown(subdir, 1, 2); err != nil {
+	diffSize, err := driver.DiffSize(upper, "")
+	if err != nil {
 		t.Fatal(err)
 	}
 
-	file := path.Join(dir, "a file")
-	if err := ioutil.WriteFile(file, []byte("Some data"), 0222|os.ModeSetuid); err != nil {
+	diff := stringid.GenerateRandomID()
+	if err := driver.Create(diff, base, "", nil); err != nil {
 		t.Fatal(err)
 	}
-}
 
-func verifyBase(t *testing.T, driver graphdriver.Driver, name string) {
-	dir, err := driver.Get(name, "")
-	if err != nil {
+	if err := checkManyFiles(driver, diff, fileCount, 3); err != nil {
 		t.Fatal(err)
 	}
-	defer driver.Put(name)
 
-	subdir := path.Join(dir, "a subdir")
-	verifyFile(t, subdir, 0705|os.ModeDir|os.ModeSticky, 1, 2)
+	arch, err := driver.Diff(upper, base)
+	if err != nil {
+		t.Fatal(err)
+	}
 
-	file := path.Join(dir, "a file")
-	verifyFile(t, file, 0222|os.ModeSetuid, 0, 0)
+	buf := bytes.NewBuffer(nil)
+	if _, err := buf.ReadFrom(arch); err != nil {
+		t.Fatal(err)
+	}
+	if err := arch.Close(); err != nil {
+		t.Fatal(err)
+	}
 
-	fis, err := readDir(dir)
+	applyDiffSize, err := driver.ApplyDiff(diff, base, bytes.NewReader(buf.Bytes()))
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	if len(fis) != 2 {
-		t.Fatal("Unexpected files in base image")
+	if applyDiffSize != diffSize {
+		t.Fatalf("Apply diff size different, got %d, expected %d", applyDiffSize, diffSize)
+	}
+	if err := checkManyFiles(driver, diff, fileCount, 6); err != nil {
+		t.Fatal(err)
 	}
 }
 
-// DriverTestCreateBase create a base driver and verify.
-func DriverTestCreateBase(t *testing.T, drivername string) {
-	driver := GetDriver(t, drivername)
+// DriverTestChanges tests computed changes on a layer matches changes made
+func DriverTestChanges(t testing.TB, drivername string, driverOptions ...string) {
+	driver := GetDriver(t, drivername, driverOptions...)
 	defer PutDriver(t)
+	base := stringid.GenerateRandomID()
+	upper := stringid.GenerateRandomID()
 
-	createBase(t, driver, "Base")
-	defer func() {
-		if err := driver.Remove("Base"); err != nil {
-			t.Fatal(err)
-		}
-	}()
-	verifyBase(t, driver, "Base")
-}
+	if err := driver.Create(base, "", "", nil); err != nil {
+		t.Fatal(err)
+	}
 
-// DriverTestCreateSnap Create a driver and snap and verify.
-func DriverTestCreateSnap(t *testing.T, drivername string) {
-	driver := GetDriver(t, drivername)
-	defer PutDriver(t)
+	if err := addManyFiles(driver, base, 20, 3); err != nil {
+		t.Fatal(err)
+	}
 
-	createBase(t, driver, "Base")
+	if err := driver.Create(upper, base, "", nil); err != nil {
+		t.Fatal(err)
+	}
 
-	defer func() {
-		if err := driver.Remove("Base"); err != nil {
-			t.Fatal(err)
-		}
-	}()
+	expectedChanges, err := changeManyFiles(driver, upper, 20, 6)
+	if err != nil {
+		t.Fatal(err)
+	}
 
-	if err := driver.Create("Snap", "Base", "", nil); err != nil {
+	changes, err := driver.Changes(upper, base)
+	if err != nil {
 		t.Fatal(err)
 	}
-	defer func() {
-		if err := driver.Remove("Snap"); err != nil {
-			t.Fatal(err)
-		}
-	}()
 
-	verifyBase(t, driver, "Snap")
+	if err = checkChanges(expectedChanges, changes); err != nil {
+		t.Fatal(err)
+	}
 }
 
 func writeRandomFile(path string, size uint64) error {

+ 301 - 0
daemon/graphdriver/graphtest/testutil.go

@@ -0,0 +1,301 @@
+package graphtest
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"math/rand"
+	"os"
+	"path"
+	"sort"
+
+	"github.com/docker/docker/daemon/graphdriver"
+	"github.com/docker/docker/pkg/archive"
+	"github.com/docker/docker/pkg/stringid"
+)
+
+func randomContent(size int, seed int64) []byte {
+	s := rand.NewSource(seed)
+	content := make([]byte, size)
+
+	for i := 0; i < len(content); i += 7 {
+		val := s.Int63()
+		for j := 0; i+j < len(content) && j < 7; j++ {
+			content[i+j] = byte(val)
+			val >>= 8
+		}
+	}
+
+	return content
+}
+
+func addFiles(drv graphdriver.Driver, layer string, seed int64) error {
+	root, err := drv.Get(layer, "")
+	if err != nil {
+		return err
+	}
+	defer drv.Put(layer)
+
+	if err := ioutil.WriteFile(path.Join(root, "file-a"), randomContent(64, seed), 0755); err != nil {
+		return err
+	}
+	if err := os.MkdirAll(path.Join(root, "dir-b"), 0755); err != nil {
+		return err
+	}
+	if err := ioutil.WriteFile(path.Join(root, "dir-b", "file-b"), randomContent(128, seed+1), 0755); err != nil {
+		return err
+	}
+
+	return ioutil.WriteFile(path.Join(root, "file-c"), randomContent(128*128, seed+2), 0755)
+}
+
+func checkFile(drv graphdriver.Driver, layer, filename string, content []byte) error {
+	root, err := drv.Get(layer, "")
+	if err != nil {
+		return err
+	}
+	defer drv.Put(layer)
+
+	fileContent, err := ioutil.ReadFile(path.Join(root, filename))
+	if err != nil {
+		return err
+	}
+
+	if bytes.Compare(fileContent, content) != 0 {
+		return fmt.Errorf("mismatched file content %v, expecting %v", fileContent, content)
+	}
+
+	return nil
+}
+
+func addFile(drv graphdriver.Driver, layer, filename string, content []byte) error {
+	root, err := drv.Get(layer, "")
+	if err != nil {
+		return err
+	}
+	defer drv.Put(layer)
+
+	return ioutil.WriteFile(path.Join(root, filename), content, 0755)
+}
+
+func addManyFiles(drv graphdriver.Driver, layer string, count int, seed int64) error {
+	root, err := drv.Get(layer, "")
+	if err != nil {
+		return err
+	}
+	defer drv.Put(layer)
+
+	for i := 0; i < count; i += 100 {
+		dir := path.Join(root, fmt.Sprintf("directory-%d", i))
+		if err := os.MkdirAll(dir, 0755); err != nil {
+			return err
+		}
+		for j := 0; i+j < count && j < 100; j++ {
+			file := path.Join(dir, fmt.Sprintf("file-%d", i+j))
+			if err := ioutil.WriteFile(file, randomContent(64, seed+int64(i+j)), 0755); err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+func changeManyFiles(drv graphdriver.Driver, layer string, count int, seed int64) ([]archive.Change, error) {
+	root, err := drv.Get(layer, "")
+	if err != nil {
+		return nil, err
+	}
+	defer drv.Put(layer)
+
+	changes := []archive.Change{}
+	for i := 0; i < count; i += 100 {
+		archiveRoot := fmt.Sprintf("/directory-%d", i)
+		if err := os.MkdirAll(path.Join(root, archiveRoot), 0755); err != nil {
+			return nil, err
+		}
+		for j := 0; i+j < count && j < 100; j++ {
+			if j == 0 {
+				changes = append(changes, archive.Change{
+					Path: archiveRoot,
+					Kind: archive.ChangeModify,
+				})
+			}
+			var change archive.Change
+			switch j % 3 {
+			// Update file
+			case 0:
+				change.Path = path.Join(archiveRoot, fmt.Sprintf("file-%d", i+j))
+				change.Kind = archive.ChangeModify
+				if err := ioutil.WriteFile(path.Join(root, change.Path), randomContent(64, seed+int64(i+j)), 0755); err != nil {
+					return nil, err
+				}
+			// Add file
+			case 1:
+				change.Path = path.Join(archiveRoot, fmt.Sprintf("file-%d-%d", seed, i+j))
+				change.Kind = archive.ChangeAdd
+				if err := ioutil.WriteFile(path.Join(root, change.Path), randomContent(64, seed+int64(i+j)), 0755); err != nil {
+					return nil, err
+				}
+			// Remove file
+			case 2:
+				change.Path = path.Join(archiveRoot, fmt.Sprintf("file-%d", i+j))
+				change.Kind = archive.ChangeDelete
+				if err := os.Remove(path.Join(root, change.Path)); err != nil {
+					return nil, err
+				}
+			}
+			changes = append(changes, change)
+		}
+	}
+
+	return changes, nil
+}
+
+func checkManyFiles(drv graphdriver.Driver, layer string, count int, seed int64) error {
+	root, err := drv.Get(layer, "")
+	if err != nil {
+		return err
+	}
+	defer drv.Put(layer)
+
+	for i := 0; i < count; i += 100 {
+		dir := path.Join(root, fmt.Sprintf("directory-%d", i))
+		for j := 0; i+j < count && j < 100; j++ {
+			file := path.Join(dir, fmt.Sprintf("file-%d", i+j))
+			fileContent, err := ioutil.ReadFile(file)
+			if err != nil {
+				return err
+			}
+
+			content := randomContent(64, seed+int64(i+j))
+
+			if bytes.Compare(fileContent, content) != 0 {
+				return fmt.Errorf("mismatched file content %v, expecting %v", fileContent, content)
+			}
+		}
+	}
+
+	return nil
+}
+
+type changeList []archive.Change
+
+func (c changeList) Less(i, j int) bool {
+	if c[i].Path == c[j].Path {
+		return c[i].Kind < c[j].Kind
+	}
+	return c[i].Path < c[j].Path
+}
+func (c changeList) Len() int      { return len(c) }
+func (c changeList) Swap(i, j int) { c[j], c[i] = c[i], c[j] }
+
+func checkChanges(expected, actual []archive.Change) error {
+	if len(expected) != len(actual) {
+		return fmt.Errorf("unexpected number of changes, expected %d, got %d", len(expected), len(actual))
+	}
+	sort.Sort(changeList(expected))
+	sort.Sort(changeList(actual))
+
+	for i := range expected {
+		if expected[i] != actual[i] {
+			return fmt.Errorf("unexpected change, expecting %v, got %v", expected[i], actual[i])
+		}
+	}
+
+	return nil
+}
+
+func addLayerFiles(drv graphdriver.Driver, layer, parent string, i int) error {
+	root, err := drv.Get(layer, "")
+	if err != nil {
+		return err
+	}
+	defer drv.Put(layer)
+
+	if err := ioutil.WriteFile(path.Join(root, "top-id"), []byte(layer), 0755); err != nil {
+		return err
+	}
+	layerDir := path.Join(root, fmt.Sprintf("layer-%d", i))
+	if err := os.MkdirAll(layerDir, 0755); err != nil {
+		return err
+	}
+	if err := ioutil.WriteFile(path.Join(layerDir, "layer-id"), []byte(layer), 0755); err != nil {
+		return err
+	}
+	if err := ioutil.WriteFile(path.Join(layerDir, "parent-id"), []byte(parent), 0755); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func addManyLayers(drv graphdriver.Driver, baseLayer string, count int) (string, error) {
+	lastLayer := baseLayer
+	for i := 1; i <= count; i++ {
+		nextLayer := stringid.GenerateRandomID()
+		if err := drv.Create(nextLayer, lastLayer, "", nil); err != nil {
+			return "", err
+		}
+		if err := addLayerFiles(drv, nextLayer, lastLayer, i); err != nil {
+			return "", err
+		}
+
+		lastLayer = nextLayer
+
+	}
+	return lastLayer, nil
+}
+
+func checkManyLayers(drv graphdriver.Driver, layer string, count int) error {
+	root, err := drv.Get(layer, "")
+	if err != nil {
+		return err
+	}
+	defer drv.Put(layer)
+
+	layerIDBytes, err := ioutil.ReadFile(path.Join(root, "top-id"))
+	if err != nil {
+		return err
+	}
+
+	if bytes.Compare(layerIDBytes, []byte(layer)) != 0 {
+		return fmt.Errorf("mismatched file content %v, expecting %v", layerIDBytes, []byte(layer))
+	}
+
+	for i := count; i > 0; i-- {
+		layerDir := path.Join(root, fmt.Sprintf("layer-%d", i))
+
+		thisLayerIDBytes, err := ioutil.ReadFile(path.Join(layerDir, "layer-id"))
+		if err != nil {
+			return err
+		}
+		if bytes.Compare(thisLayerIDBytes, layerIDBytes) != 0 {
+			return fmt.Errorf("mismatched file content %v, expecting %v", thisLayerIDBytes, layerIDBytes)
+		}
+		layerIDBytes, err = ioutil.ReadFile(path.Join(layerDir, "parent-id"))
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// readDir reads a directory just like ioutil.ReadDir()
+// then hides specific files (currently "lost+found")
+// so the tests don't "see" it
+func readDir(dir string) ([]os.FileInfo, error) {
+	a, err := ioutil.ReadDir(dir)
+	if err != nil {
+		return nil, err
+	}
+
+	b := a[:0]
+	for _, x := range a {
+		if x.Name() != "lost+found" { // ext4 always have this dir
+			b = append(b, x)
+		}
+	}
+
+	return b, nil
+}

+ 143 - 0
daemon/graphdriver/graphtest/testutil_unix.go

@@ -0,0 +1,143 @@
+// +build linux freebsd
+
+package graphtest
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"syscall"
+	"testing"
+
+	"github.com/docker/docker/daemon/graphdriver"
+)
+
+// InitLoopbacks ensures that the loopback devices are properly created within
+// the system running the device mapper tests.
+func InitLoopbacks() error {
+	statT, err := getBaseLoopStats()
+	if err != nil {
+		return err
+	}
+	// create at least 8 loopback files, ya, that is a good number
+	for i := 0; i < 8; i++ {
+		loopPath := fmt.Sprintf("/dev/loop%d", i)
+		// only create new loopback files if they don't exist
+		if _, err := os.Stat(loopPath); err != nil {
+			if mkerr := syscall.Mknod(loopPath,
+				uint32(statT.Mode|syscall.S_IFBLK), int((7<<8)|(i&0xff)|((i&0xfff00)<<12))); mkerr != nil {
+				return mkerr
+			}
+			os.Chown(loopPath, int(statT.Uid), int(statT.Gid))
+		}
+	}
+	return nil
+}
+
+// getBaseLoopStats inspects /dev/loop0 to collect uid,gid, and mode for the
+// loop0 device on the system.  If it does not exist we assume 0,0,0660 for the
+// stat data
+func getBaseLoopStats() (*syscall.Stat_t, error) {
+	loop0, err := os.Stat("/dev/loop0")
+	if err != nil {
+		if os.IsNotExist(err) {
+			return &syscall.Stat_t{
+				Uid:  0,
+				Gid:  0,
+				Mode: 0660,
+			}, nil
+		}
+		return nil, err
+	}
+	return loop0.Sys().(*syscall.Stat_t), nil
+}
+
+func verifyFile(t testing.TB, path string, mode os.FileMode, uid, gid uint32) {
+	fi, err := os.Stat(path)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if fi.Mode()&os.ModeType != mode&os.ModeType {
+		t.Fatalf("Expected %s type 0x%x, got 0x%x", path, mode&os.ModeType, fi.Mode()&os.ModeType)
+	}
+
+	if fi.Mode()&os.ModePerm != mode&os.ModePerm {
+		t.Fatalf("Expected %s mode %o, got %o", path, mode&os.ModePerm, fi.Mode()&os.ModePerm)
+	}
+
+	if fi.Mode()&os.ModeSticky != mode&os.ModeSticky {
+		t.Fatalf("Expected %s sticky 0x%x, got 0x%x", path, mode&os.ModeSticky, fi.Mode()&os.ModeSticky)
+	}
+
+	if fi.Mode()&os.ModeSetuid != mode&os.ModeSetuid {
+		t.Fatalf("Expected %s setuid 0x%x, got 0x%x", path, mode&os.ModeSetuid, fi.Mode()&os.ModeSetuid)
+	}
+
+	if fi.Mode()&os.ModeSetgid != mode&os.ModeSetgid {
+		t.Fatalf("Expected %s setgid 0x%x, got 0x%x", path, mode&os.ModeSetgid, fi.Mode()&os.ModeSetgid)
+	}
+
+	if stat, ok := fi.Sys().(*syscall.Stat_t); ok {
+		if stat.Uid != uid {
+			t.Fatalf("%s no owned by uid %d", path, uid)
+		}
+		if stat.Gid != gid {
+			t.Fatalf("%s not owned by gid %d", path, gid)
+		}
+	}
+}
+
+func createBase(t testing.TB, driver graphdriver.Driver, name string) {
+	// We need to be able to set any perms
+	oldmask := syscall.Umask(0)
+	defer syscall.Umask(oldmask)
+
+	if err := driver.CreateReadWrite(name, "", "", nil); err != nil {
+		t.Fatal(err)
+	}
+
+	dir, err := driver.Get(name, "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer driver.Put(name)
+
+	subdir := path.Join(dir, "a subdir")
+	if err := os.Mkdir(subdir, 0705|os.ModeSticky); err != nil {
+		t.Fatal(err)
+	}
+	if err := os.Chown(subdir, 1, 2); err != nil {
+		t.Fatal(err)
+	}
+
+	file := path.Join(dir, "a file")
+	if err := ioutil.WriteFile(file, []byte("Some data"), 0222|os.ModeSetuid); err != nil {
+		t.Fatal(err)
+	}
+}
+
+func verifyBase(t testing.TB, driver graphdriver.Driver, name string) {
+	dir, err := driver.Get(name, "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer driver.Put(name)
+
+	subdir := path.Join(dir, "a subdir")
+	verifyFile(t, subdir, 0705|os.ModeDir|os.ModeSticky, 1, 2)
+
+	file := path.Join(dir, "a file")
+	verifyFile(t, file, 0222|os.ModeSetuid, 0, 0)
+
+	fis, err := readDir(dir)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if len(fis) != 2 {
+		t.Fatal("Unexpected files in base image")
+	}
+
+}