Преглед изворни кода

Merge pull request #62 from shykes/devmapper-unit-tests

Devmapper unit tests
Michael Crosby пре 11 година
родитељ
комит
437bdeee59

+ 29 - 32
graphdriver/devmapper/deviceset.go

@@ -6,13 +6,10 @@ import (
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io/ioutil"
-	"os"
-	"os/exec"
 	"path"
 	"path/filepath"
 	"strconv"
 	"sync"
-	"syscall"
 	"time"
 )
 
@@ -105,7 +102,7 @@ func (devices *DeviceSet) hasImage(name string) bool {
 	dirname := devices.loopbackDir()
 	filename := path.Join(dirname, name)
 
-	_, err := os.Stat(filename)
+	_, err := osStat(filename)
 	return err == nil
 }
 
@@ -117,16 +114,16 @@ func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) {
 	dirname := devices.loopbackDir()
 	filename := path.Join(dirname, name)
 
-	if err := os.MkdirAll(dirname, 0700); err != nil && !os.IsExist(err) {
+	if err := osMkdirAll(dirname, 0700); err != nil && !osIsExist(err) {
 		return "", err
 	}
 
-	if _, err := os.Stat(filename); err != nil {
-		if !os.IsNotExist(err) {
+	if _, err := osStat(filename); err != nil {
+		if !osIsNotExist(err) {
 			return "", err
 		}
 		utils.Debugf("Creating loopback file %s for device-manage use", filename)
-		file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600)
+		file, err := osOpenFile(filename, osORdWr|osOCreate, 0600)
 		if err != nil {
 			return "", err
 		}
@@ -174,7 +171,7 @@ func (devices *DeviceSet) saveMetadata() error {
 	if err := tmpFile.Close(); err != nil {
 		return fmt.Errorf("Error closing metadata file %s: %s", tmpFile.Name(), err)
 	}
-	if err := os.Rename(tmpFile.Name(), devices.jsonFile()); err != nil {
+	if err := osRename(tmpFile.Name(), devices.jsonFile()); err != nil {
 		return fmt.Errorf("Error committing metadata file", err)
 	}
 
@@ -225,9 +222,9 @@ func (devices *DeviceSet) activateDeviceIfNeeded(hash string) error {
 func (devices *DeviceSet) createFilesystem(info *DevInfo) error {
 	devname := info.DevName()
 
-	err := exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run()
+	err := execRun("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname)
 	if err != nil {
-		err = exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname).Run()
+		err = execRun("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname)
 	}
 	if err != nil {
 		utils.Debugf("\n--->Err: %s\n", err)
@@ -252,7 +249,7 @@ func (devices *DeviceSet) loadMetaData() error {
 	devices.NewTransactionId = devices.TransactionId
 
 	jsonData, err := ioutil.ReadFile(devices.jsonFile())
-	if err != nil && !os.IsNotExist(err) {
+	if err != nil && !osIsNotExist(err) {
 		utils.Debugf("\n--->Err: %s\n", err)
 		return err
 	}
@@ -337,14 +334,13 @@ func (devices *DeviceSet) setupBaseImage() error {
 }
 
 func setCloseOnExec(name string) {
-	fileInfos, _ := ioutil.ReadDir("/proc/self/fd")
-	if fileInfos != nil {
+	if fileInfos, _ := ioutil.ReadDir("/proc/self/fd"); fileInfos != nil {
 		for _, i := range fileInfos {
-			link, _ := os.Readlink(filepath.Join("/proc/self/fd", i.Name()))
+			link, _ := osReadlink(filepath.Join("/proc/self/fd", i.Name()))
 			if link == name {
 				fd, err := strconv.Atoi(i.Name())
 				if err == nil {
-					syscall.CloseOnExec(fd)
+					sysCloseOnExec(fd)
 				}
 			}
 		}
@@ -372,7 +368,7 @@ func (devices *DeviceSet) ResizePool(size int64) error {
 	datafilename := path.Join(dirname, "data")
 	metadatafilename := path.Join(dirname, "metadata")
 
-	datafile, err := os.OpenFile(datafilename, os.O_RDWR, 0)
+	datafile, err := osOpenFile(datafilename, osORdWr, 0)
 	if datafile == nil {
 		return err
 	}
@@ -387,19 +383,19 @@ func (devices *DeviceSet) ResizePool(size int64) error {
 		return fmt.Errorf("Can't shrink file")
 	}
 
-	dataloopback := FindLoopDeviceFor(datafile)
+	dataloopback := FindLoopDeviceFor(&osFile{File: datafile})
 	if dataloopback == nil {
 		return fmt.Errorf("Unable to find loopback mount for: %s", datafilename)
 	}
 	defer dataloopback.Close()
 
-	metadatafile, err := os.OpenFile(metadatafilename, os.O_RDWR, 0)
+	metadatafile, err := osOpenFile(metadatafilename, osORdWr, 0)
 	if metadatafile == nil {
 		return err
 	}
 	defer metadatafile.Close()
 
-	metadataloopback := FindLoopDeviceFor(metadatafile)
+	metadataloopback := FindLoopDeviceFor(&osFile{File: metadatafile})
 	if metadataloopback == nil {
 		return fmt.Errorf("Unable to find loopback mount for: %s", metadatafilename)
 	}
@@ -464,11 +460,11 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error {
 
 	// Set the device prefix from the device id and inode of the docker root dir
 
-	st, err := os.Stat(devices.root)
+	st, err := osStat(devices.root)
 	if err != nil {
 		return fmt.Errorf("Error looking up dir %s: %s", devices.root, err)
 	}
-	sysSt := st.Sys().(*syscall.Stat_t)
+	sysSt := toSysStatT(st.Sys())
 	// "reg-" stands for "regular file".
 	// In the future we might use "dev-" for "device file", etc.
 	// docker-maj,min[-inode] stands for:
@@ -708,15 +704,16 @@ func (devices *DeviceSet) byHash(hash string) (devname string, err error) {
 }
 
 func (devices *DeviceSet) Shutdown() error {
-	utils.Debugf("[deviceset %s] shutdown()", devices.devicePrefix)
-	defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix)
 	devices.Lock()
-	utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root)
 	defer devices.Unlock()
 
+	utils.Debugf("[deviceset %s] shutdown()", devices.devicePrefix)
+	utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root)
+	defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix)
+
 	for path, count := range devices.activeMounts {
 		for i := count; i > 0; i-- {
-			if err := syscall.Unmount(path, 0); err != nil {
+			if err := sysUnmount(path, 0); err != nil {
 				utils.Debugf("Shutdown unmounting %s, error: %s\n", path, err)
 			}
 		}
@@ -752,15 +749,15 @@ func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error {
 
 	info := devices.Devices[hash]
 
-	var flags uintptr = syscall.MS_MGC_VAL
+	var flags uintptr = sysMsMgcVal
 
 	if readOnly {
-		flags = flags | syscall.MS_RDONLY
+		flags = flags | sysMsRdOnly
 	}
 
-	err := syscall.Mount(info.DevName(), path, "ext4", flags, "discard")
-	if err != nil && err == syscall.EINVAL {
-		err = syscall.Mount(info.DevName(), path, "ext4", flags, "")
+	err := sysMount(info.DevName(), path, "ext4", flags, "discard")
+	if err != nil && err == sysEInval {
+		err = sysMount(info.DevName(), path, "ext4", flags, "")
 	}
 	if err != nil {
 		return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err)
@@ -779,7 +776,7 @@ func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) erro
 	defer devices.Unlock()
 
 	utils.Debugf("[devmapper] Unmount(%s)", path)
-	if err := syscall.Unmount(path, 0); err != nil {
+	if err := sysUnmount(path, 0); err != nil {
 		utils.Debugf("\n--->Err: %s\n", err)
 		return err
 	}

+ 17 - 20
graphdriver/devmapper/devmapper.go

@@ -4,9 +4,7 @@ import (
 	"errors"
 	"fmt"
 	"github.com/dotcloud/docker/utils"
-	"os"
 	"runtime"
-	"syscall"
 )
 
 type DevmapperLogger interface {
@@ -49,7 +47,6 @@ var (
 	ErrTaskAddTarget          = errors.New("dm_task_add_target failed")
 	ErrTaskSetSector          = errors.New("dm_task_set_sector failed")
 	ErrTaskGetInfo            = errors.New("dm_task_get_info failed")
-	ErrTaskGetDriverVersion   = errors.New("dm_task_get_driver_version failed")
 	ErrTaskSetCookie          = errors.New("dm_task_set_cookie failed")
 	ErrNilCookie              = errors.New("cookie ptr can't be nil")
 	ErrAttachLoopbackDevice   = errors.New("loopback mounting failed")
@@ -86,7 +83,7 @@ type (
 
 func (t *Task) destroy() {
 	if t != nil {
-		DmTaskDestory(t.unmanaged)
+		DmTaskDestroy(t.unmanaged)
 		runtime.SetFinalizer(t, nil)
 	}
 }
@@ -180,16 +177,16 @@ func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64,
 		start, length, targetType, params
 }
 
-func AttachLoopDevice(filename string) (*os.File, error) {
+func AttachLoopDevice(filename string) (*osFile, error) {
 	var fd int
 	res := DmAttachLoopDevice(filename, &fd)
 	if res == "" {
 		return nil, ErrAttachLoopbackDevice
 	}
-	return os.NewFile(uintptr(fd), res), nil
+	return &osFile{File: osNewFile(uintptr(fd), res)}, nil
 }
 
-func getLoopbackBackingFile(file *os.File) (uint64, uint64, error) {
+func getLoopbackBackingFile(file *osFile) (uint64, uint64, error) {
 	dev, inode, err := dmGetLoopbackBackingFile(file.Fd())
 	if err != 0 {
 		return 0, 0, ErrGetLoopbackBackingFile
@@ -197,7 +194,7 @@ func getLoopbackBackingFile(file *os.File) (uint64, uint64, error) {
 	return dev, inode, nil
 }
 
-func LoopbackSetCapacity(file *os.File) error {
+func LoopbackSetCapacity(file *osFile) error {
 	err := dmLoopbackSetCapacity(file.Fd())
 	if err != 0 {
 		return ErrLoopbackSetCapacity
@@ -205,20 +202,20 @@ func LoopbackSetCapacity(file *os.File) error {
 	return nil
 }
 
-func FindLoopDeviceFor(file *os.File) *os.File {
+func FindLoopDeviceFor(file *osFile) *osFile {
 	stat, err := file.Stat()
 	if err != nil {
 		return nil
 	}
-	targetInode := stat.Sys().(*syscall.Stat_t).Ino
-	targetDevice := stat.Sys().(*syscall.Stat_t).Dev
+	targetInode := stat.Sys().(*sysStatT).Ino
+	targetDevice := stat.Sys().(*sysStatT).Dev
 
 	for i := 0; true; i++ {
 		path := fmt.Sprintf("/dev/loop%d", i)
 
-		file, err := os.OpenFile(path, os.O_RDWR, 0)
+		file, err := osOpenFile(path, osORdWr, 0)
 		if err != nil {
-			if os.IsNotExist(err) {
+			if osIsNotExist(err) {
 				return nil
 			}
 
@@ -227,9 +224,9 @@ func FindLoopDeviceFor(file *os.File) *os.File {
 			continue
 		}
 
-		dev, inode, err := getLoopbackBackingFile(file)
+		dev, inode, err := getLoopbackBackingFile(&osFile{File: file})
 		if err == nil && dev == targetDevice && inode == targetInode {
-			return file
+			return &osFile{File: file}
 		}
 
 		file.Close()
@@ -289,7 +286,7 @@ func RemoveDevice(name string) error {
 	return nil
 }
 
-func GetBlockDeviceSize(file *os.File) (uint64, error) {
+func GetBlockDeviceSize(file *osFile) (uint64, error) {
 	size, errno := DmGetBlockSize(file.Fd())
 	if size == -1 || errno != 0 {
 		return 0, ErrGetBlockSize
@@ -298,7 +295,7 @@ func GetBlockDeviceSize(file *os.File) (uint64, error) {
 }
 
 // This is the programmatic example of "dmsetup create"
-func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error {
+func createPool(poolName string, dataFile, metadataFile *osFile) error {
 	task, err := createTask(DeviceCreate, poolName)
 	if task == nil {
 		return err
@@ -328,7 +325,7 @@ func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error
 	return nil
 }
 
-func reloadPool(poolName string, dataFile *os.File, metadataFile *os.File) error {
+func reloadPool(poolName string, dataFile, metadataFile *osFile) error {
 	task, err := createTask(DeviceReload, poolName)
 	if task == nil {
 		return err
@@ -394,8 +391,8 @@ func getStatus(name string) (uint64, uint64, string, string, error) {
 		return 0, 0, "", "", fmt.Errorf("Non existing device %s", name)
 	}
 
-	_, start, length, target_type, params := task.GetNextTarget(0)
-	return start, length, target_type, params, nil
+	_, start, length, targetType, params := task.GetNextTarget(0)
+	return start, length, targetType, params, nil
 }
 
 func setTransactionId(poolName string, oldId uint64, newId uint64) error {

+ 106 - 0
graphdriver/devmapper/devmapper_doc.go

@@ -0,0 +1,106 @@
+package devmapper
+
+// Definition of struct dm_task and sub structures (from lvm2)
+//
+// struct dm_ioctl {
+// 	/*
+// 	 * The version number is made up of three parts:
+// 	 * major - no backward or forward compatibility,
+// 	 * minor - only backwards compatible,
+// 	 * patch - both backwards and forwards compatible.
+// 	 *
+// 	 * All clients of the ioctl interface should fill in the
+// 	 * version number of the interface that they were
+// 	 * compiled with.
+// 	 *
+// 	 * All recognised ioctl commands (ie. those that don't
+// 	 * return -ENOTTY) fill out this field, even if the
+// 	 * command failed.
+// 	 */
+// 	uint32_t version[3];	/* in/out */
+// 	uint32_t data_size;	/* total size of data passed in
+// 				 * including this struct */
+
+// 	uint32_t data_start;	/* offset to start of data
+// 				 * relative to start of this struct */
+
+// 	uint32_t target_count;	/* in/out */
+// 	int32_t open_count;	/* out */
+// 	uint32_t flags;		/* in/out */
+
+// 	/*
+// 	 * event_nr holds either the event number (input and output) or the
+// 	 * udev cookie value (input only).
+// 	 * The DM_DEV_WAIT ioctl takes an event number as input.
+// 	 * The DM_SUSPEND, DM_DEV_REMOVE and DM_DEV_RENAME ioctls
+// 	 * use the field as a cookie to return in the DM_COOKIE
+// 	 * variable with the uevents they issue.
+// 	 * For output, the ioctls return the event number, not the cookie.
+// 	 */
+// 	uint32_t event_nr;      	/* in/out */
+// 	uint32_t padding;
+
+// 	uint64_t dev;		/* in/out */
+
+// 	char name[DM_NAME_LEN];	/* device name */
+// 	char uuid[DM_UUID_LEN];	/* unique identifier for
+// 				 * the block device */
+// 	char data[7];		/* padding or data */
+// };
+
+// struct target {
+// 	uint64_t start;
+// 	uint64_t length;
+// 	char *type;
+// 	char *params;
+
+// 	struct target *next;
+// };
+
+// typedef enum {
+// 	DM_ADD_NODE_ON_RESUME, /* add /dev/mapper node with dmsetup resume */
+// 	DM_ADD_NODE_ON_CREATE  /* add /dev/mapper node with dmsetup create */
+// } dm_add_node_t;
+
+// struct dm_task {
+// 	int type;
+// 	char *dev_name;
+// 	char *mangled_dev_name;
+
+// 	struct target *head, *tail;
+
+// 	int read_only;
+// 	uint32_t event_nr;
+// 	int major;
+// 	int minor;
+// 	int allow_default_major_fallback;
+// 	uid_t uid;
+// 	gid_t gid;
+// 	mode_t mode;
+// 	uint32_t read_ahead;
+// 	uint32_t read_ahead_flags;
+// 	union {
+// 		struct dm_ioctl *v4;
+// 	} dmi;
+// 	char *newname;
+// 	char *message;
+// 	char *geometry;
+// 	uint64_t sector;
+// 	int no_flush;
+// 	int no_open_count;
+// 	int skip_lockfs;
+// 	int query_inactive_table;
+// 	int suppress_identical_reload;
+// 	dm_add_node_t add_node;
+// 	uint64_t existing_table_size;
+// 	int cookie_set;
+// 	int new_uuid;
+// 	int secure_data;
+// 	int retry_remove;
+// 	int enable_checks;
+// 	int expected_errno;
+
+// 	char *uuid;
+// 	char *mangled_uuid;
+// };
+//

+ 10 - 10
graphdriver/devmapper/devmapper_test.go

@@ -1,11 +1,11 @@
 package devmapper
 
 import (
-	"syscall"
 	"testing"
 )
 
 func TestTaskCreate(t *testing.T) {
+	t.Skip("FIXME: not a unit test")
 	// Test success
 	taskCreate(t, DeviceInfo)
 
@@ -18,6 +18,7 @@ func TestTaskCreate(t *testing.T) {
 }
 
 func TestTaskRun(t *testing.T) {
+	t.Skip("FIXME: not a unit test")
 	task := taskCreate(t, DeviceInfo)
 
 	// Test success
@@ -46,6 +47,7 @@ func TestTaskRun(t *testing.T) {
 }
 
 func TestTaskSetName(t *testing.T) {
+	t.Skip("FIXME: not a unit test")
 	task := taskCreate(t, DeviceInfo)
 
 	// Test success
@@ -63,6 +65,7 @@ func TestTaskSetName(t *testing.T) {
 }
 
 func TestTaskSetMessage(t *testing.T) {
+	t.Skip("FIXME: not a unit test")
 	task := taskCreate(t, DeviceInfo)
 
 	// Test success
@@ -80,6 +83,7 @@ func TestTaskSetMessage(t *testing.T) {
 }
 
 func TestTaskSetSector(t *testing.T) {
+	t.Skip("FIXME: not a unit test")
 	task := taskCreate(t, DeviceInfo)
 
 	// Test success
@@ -97,6 +101,7 @@ func TestTaskSetSector(t *testing.T) {
 }
 
 func TestTaskSetCookie(t *testing.T) {
+	t.Skip("FIXME: not a unit test")
 	var (
 		cookie uint = 0
 		task        = taskCreate(t, DeviceInfo)
@@ -121,6 +126,7 @@ func TestTaskSetCookie(t *testing.T) {
 }
 
 func TestTaskSetAddNode(t *testing.T) {
+	t.Skip("FIXME: not a unit test")
 	task := taskCreate(t, DeviceInfo)
 
 	// Test success
@@ -142,6 +148,7 @@ func TestTaskSetAddNode(t *testing.T) {
 }
 
 func TestTaskSetRo(t *testing.T) {
+	t.Skip("FIXME: not a unit test")
 	task := taskCreate(t, DeviceInfo)
 
 	// Test success
@@ -159,6 +166,7 @@ func TestTaskSetRo(t *testing.T) {
 }
 
 func TestTaskAddTarget(t *testing.T) {
+	t.Skip("FIXME: not a unit test")
 	task := taskCreate(t, DeviceInfo)
 
 	// Test success
@@ -247,10 +255,6 @@ func dmTaskAddTargetFail(task *CDmTask,
 	return -1
 }
 
-func dmTaskGetDriverVersionFail(task *CDmTask, version *string) int {
-	return -1
-}
-
 func dmTaskGetInfoFail(task *CDmTask, info *Info) int {
 	return -1
 }
@@ -264,14 +268,10 @@ func dmAttachLoopDeviceFail(filename string, fd *int) string {
 	return ""
 }
 
-func sysGetBlockSizeFail(fd uintptr, size *uint64) syscall.Errno {
+func sysGetBlockSizeFail(fd uintptr, size *uint64) sysErrno {
 	return 1
 }
 
-func dmGetBlockSizeFail(fd uintptr) int64 {
-	return -1
-}
-
 func dmUdevWaitFail(cookie uint) int {
 	return -1
 }

+ 28 - 32
graphdriver/devmapper/devmapper_wrapper.go

@@ -140,7 +140,6 @@ static void	log_with_errno_init()
 import "C"
 
 import (
-	"syscall"
 	"unsafe"
 )
 
@@ -149,26 +148,26 @@ type (
 )
 
 var (
-	DmTaskDestory       = dmTaskDestroyFct
+	DmAttachLoopDevice  = dmAttachLoopDeviceFct
+	DmGetBlockSize      = dmGetBlockSizeFct
+	DmGetLibraryVersion = dmGetLibraryVersionFct
+	DmGetNextTarget     = dmGetNextTargetFct
+	DmLogInitVerbose    = dmLogInitVerboseFct
+	DmSetDevDir         = dmSetDevDirFct
+	DmTaskAddTarget     = dmTaskAddTargetFct
 	DmTaskCreate        = dmTaskCreateFct
+	DmTaskDestroy       = dmTaskDestroyFct
+	DmTaskGetInfo       = dmTaskGetInfoFct
 	DmTaskRun           = dmTaskRunFct
-	DmTaskSetName       = dmTaskSetNameFct
-	DmTaskSetMessage    = dmTaskSetMessageFct
-	DmTaskSetSector     = dmTaskSetSectorFct
-	DmTaskSetCookie     = dmTaskSetCookieFct
 	DmTaskSetAddNode    = dmTaskSetAddNodeFct
+	DmTaskSetCookie     = dmTaskSetCookieFct
+	DmTaskSetMessage    = dmTaskSetMessageFct
+	DmTaskSetName       = dmTaskSetNameFct
 	DmTaskSetRo         = dmTaskSetRoFct
-	DmTaskAddTarget     = dmTaskAddTargetFct
-	DmTaskGetInfo       = dmTaskGetInfoFct
-	DmGetNextTarget     = dmGetNextTargetFct
-	DmGetBlockSize      = dmGetBlockSizeFct
-	DmAttachLoopDevice  = dmAttachLoopDeviceFct
+	DmTaskSetSector     = dmTaskSetSectorFct
 	DmUdevWait          = dmUdevWaitFct
-	DmLogInitVerbose    = dmLogInitVerboseFct
-	DmSetDevDir         = dmSetDevDirFct
-	DmGetLibraryVersion = dmGetLibraryVersionFct
-	LogWithErrnoInit    = logWithErrnoInitFct
 	GetBlockSize        = getBlockSizeFct
+	LogWithErrnoInit    = logWithErrnoInitFct
 )
 
 func free(p *C.char) {
@@ -239,23 +238,22 @@ func dmTaskAddTargetFct(task *CDmTask,
 		C.uint64_t(start), C.uint64_t(size), Cttype, Cparams))
 }
 
-func dmGetLoopbackBackingFile(fd uintptr) (uint64, uint64, syscall.Errno) {
+func dmGetLoopbackBackingFile(fd uintptr) (uint64, uint64, sysErrno) {
 	var lo64 C.struct_loop_info64
-	_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.LOOP_GET_STATUS64,
+	_, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_GET_STATUS64,
 		uintptr(unsafe.Pointer(&lo64)))
-	return uint64(lo64.lo_device), uint64(lo64.lo_inode), err
+	return uint64(lo64.lo_device), uint64(lo64.lo_inode), sysErrno(err)
 }
 
-func dmLoopbackSetCapacity(fd uintptr) syscall.Errno {
-	_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.LOOP_SET_CAPACITY, 0)
-	return err
+func dmLoopbackSetCapacity(fd uintptr) sysErrno {
+	_, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_SET_CAPACITY, 0)
+	return sysErrno(err)
 }
 
-func dmGetBlockSizeFct(fd uintptr) (int64, syscall.Errno) {
+func dmGetBlockSizeFct(fd uintptr) (int64, sysErrno) {
 	var size int64
-	_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64,
-		uintptr(unsafe.Pointer(&size)))
-	return size, err
+	_, _, err := sysSyscall(sysSysIoctl, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size)))
+	return size, sysErrno(err)
 }
 
 func dmTaskGetInfoFct(task *CDmTask, info *Info) int {
@@ -275,9 +273,7 @@ func dmTaskGetInfoFct(task *CDmTask, info *Info) int {
 	return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo))
 }
 
-func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64,
-	target, params *string) uintptr {
-
+func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
 	var (
 		Cstart, Clength      C.uint64_t
 		CtargetType, Cparams *C.char
@@ -288,6 +284,7 @@ func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64,
 		*target = C.GoString(CtargetType)
 		*params = C.GoString(Cparams)
 	}()
+
 	nextp := C.dm_get_next_target((*C.struct_dm_task)(task),
 		unsafe.Pointer(next), &Cstart, &Clength, &CtargetType, &Cparams)
 	return uintptr(nextp)
@@ -307,10 +304,9 @@ func dmAttachLoopDeviceFct(filename string, fd *int) string {
 	return C.GoString(ret)
 }
 
-func getBlockSizeFct(fd uintptr, size *uint64) syscall.Errno {
-	_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64,
-		uintptr(unsafe.Pointer(&size)))
-	return err
+func getBlockSizeFct(fd uintptr, size *uint64) sysErrno {
+	_, _, err := sysSyscall(sysSysIoctl, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size)))
+	return sysErrno(err)
 }
 
 func dmUdevWaitFct(cookie uint) int {

+ 4 - 5
graphdriver/devmapper/driver.go

@@ -4,7 +4,6 @@ import (
 	"fmt"
 	"github.com/dotcloud/docker/graphdriver"
 	"io/ioutil"
-	"os"
 	"path"
 )
 
@@ -22,7 +21,7 @@ type Driver struct {
 	home string
 }
 
-func Init(home string) (graphdriver.Driver, error) {
+var Init = func(home string) (graphdriver.Driver, error) {
 	deviceSet, err := NewDeviceSet(home, true)
 	if err != nil {
 		return nil, err
@@ -57,7 +56,7 @@ func (d *Driver) Cleanup() error {
 	return d.DeviceSet.Shutdown()
 }
 
-func (d *Driver) Create(id string, parent string) error {
+func (d *Driver) Create(id, parent string) error {
 	if err := d.DeviceSet.AddDevice(id, parent); err != nil {
 		return err
 	}
@@ -67,7 +66,7 @@ func (d *Driver) Create(id string, parent string) error {
 		return err
 	}
 
-	if err := os.MkdirAll(path.Join(mp, "rootfs"), 0755); err != nil && !os.IsExist(err) {
+	if err := osMkdirAll(path.Join(mp, "rootfs"), 0755); err != nil && !osIsExist(err) {
 		return err
 	}
 
@@ -98,7 +97,7 @@ func (d *Driver) Get(id string) (string, error) {
 
 func (d *Driver) mount(id, mountPoint string) error {
 	// Create the target directories if they don't exist
-	if err := os.MkdirAll(mountPoint, 0755); err != nil && !os.IsExist(err) {
+	if err := osMkdirAll(mountPoint, 0755); err != nil && !osIsExist(err) {
 		return err
 	}
 	// If mountpoint is already mounted, do nothing

+ 606 - 37
graphdriver/devmapper/driver_test.go

@@ -1,9 +1,13 @@
 package devmapper
 
 import (
+	"fmt"
+	"github.com/dotcloud/docker/graphdriver"
 	"io/ioutil"
-	"os"
 	"path"
+	"runtime"
+	"strings"
+	"syscall"
 	"testing"
 )
 
@@ -12,7 +16,105 @@ func init() {
 	DefaultDataLoopbackSize = 300 * 1024 * 1024
 	DefaultMetaDataLoopbackSize = 200 * 1024 * 1024
 	DefaultBaseFsSize = 300 * 1024 * 1024
+}
+
+// denyAllDevmapper mocks all calls to libdevmapper in the unit tests, and denies them by default
+func denyAllDevmapper() {
+	// Hijack all calls to libdevmapper with default panics.
+	// Authorized calls are selectively hijacked in each tests.
+	DmTaskCreate = func(t int) *CDmTask {
+		panic("DmTaskCreate: this method should not be called here")
+	}
+	DmTaskRun = func(task *CDmTask) int {
+		panic("DmTaskRun: this method should not be called here")
+	}
+	DmTaskSetName = func(task *CDmTask, name string) int {
+		panic("DmTaskSetName: this method should not be called here")
+	}
+	DmTaskSetMessage = func(task *CDmTask, message string) int {
+		panic("DmTaskSetMessage: this method should not be called here")
+	}
+	DmTaskSetSector = func(task *CDmTask, sector uint64) int {
+		panic("DmTaskSetSector: this method should not be called here")
+	}
+	DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int {
+		panic("DmTaskSetCookie: this method should not be called here")
+	}
+	DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int {
+		panic("DmTaskSetAddNode: this method should not be called here")
+	}
+	DmTaskSetRo = func(task *CDmTask) int {
+		panic("DmTaskSetRo: this method should not be called here")
+	}
+	DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
+		panic("DmTaskAddTarget: this method should not be called here")
+	}
+	DmTaskGetInfo = func(task *CDmTask, info *Info) int {
+		panic("DmTaskGetInfo: this method should not be called here")
+	}
+	DmGetNextTarget = func(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
+		panic("DmGetNextTarget: this method should not be called here")
+	}
+	DmAttachLoopDevice = func(filename string, fd *int) string {
+		panic("DmAttachLoopDevice: this method should not be called here")
+	}
+	DmGetBlockSize = func(fd uintptr) (int64, sysErrno) {
+		panic("DmGetBlockSize: this method should not be called here")
+	}
+	DmUdevWait = func(cookie uint) int {
+		panic("DmUdevWait: this method should not be called here")
+	}
+	DmSetDevDir = func(dir string) int {
+		panic("DmSetDevDir: this method should not be called here")
+	}
+	DmGetLibraryVersion = func(version *string) int {
+		panic("DmGetLibraryVersion: this method should not be called here")
+	}
+	DmLogInitVerbose = func(level int) {
+		panic("DmLogInitVerbose: this method should not be called here")
+	}
+	DmTaskDestroy = func(task *CDmTask) {
+		panic("DmTaskDestroy: this method should not be called here")
+	}
+	GetBlockSize = func(fd uintptr, size *uint64) sysErrno {
+		panic("GetBlockSize: this method should not be called here")
+	}
+	LogWithErrnoInit = func() {
+		panic("LogWithErrnoInit: this method should not be called here")
+	}
+}
+
+func denyAllSyscall() {
+	sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) {
+		panic("sysMount: this method should not be called here")
+	}
+	sysUnmount = func(target string, flags int) (err error) {
+		panic("sysUnmount: this method should not be called here")
+	}
+	sysCloseOnExec = func(fd int) {
+		panic("sysCloseOnExec: this method should not be called here")
+	}
+	sysSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
+		panic("sysSyscall: this method should not be called here")
+	}
+	// Not a syscall, but forbidding it here anyway
+	Mounted = func(mnt string) (bool, error) {
+		panic("devmapper.Mounted: this method should not be called here")
+	}
+	// osOpenFile = os.OpenFile
+	// osNewFile = os.NewFile
+	// osCreate = os.Create
+	// osStat = os.Stat
+	// osIsNotExist = os.IsNotExist
+	// osIsExist = os.IsExist
+	// osMkdirAll = os.MkdirAll
+	// osRemoveAll = os.RemoveAll
+	// osRename = os.Rename
+	// osReadlink = os.Readlink
 
+	// execRun = func(name string, args ...string) error {
+	// 	return exec.Command(name, args...).Run()
+	// }
 }
 
 func mkTestDirectory(t *testing.T) string {
@@ -34,72 +136,521 @@ func newDriver(t *testing.T) *Driver {
 
 func cleanup(d *Driver) {
 	d.Cleanup()
-	os.RemoveAll(d.home)
+	osRemoveAll(d.home)
 }
 
-func TestInit(t *testing.T) {
-	home := mkTestDirectory(t)
-	defer os.RemoveAll(home)
-	driver, err := Init(home)
-	if err != nil {
-		t.Fatal(err)
+type Set map[string]bool
+
+func (r Set) Assert(t *testing.T, names ...string) {
+	for _, key := range names {
+		if _, exists := r[key]; !exists {
+			t.Fatalf("Key not set: %s", key)
+		}
+		delete(r, key)
 	}
-	defer func() {
-		if err := driver.Cleanup(); err != nil {
+	if len(r) != 0 {
+		t.Fatalf("Unexpected keys: %v", r)
+	}
+}
+
+func TestInit(t *testing.T) {
+	var (
+		calls           = make(Set)
+		devicesAttached = make(Set)
+		taskMessages    = make(Set)
+		taskTypes       = make(Set)
+		home            = mkTestDirectory(t)
+	)
+	defer osRemoveAll(home)
+
+	func() {
+		denyAllDevmapper()
+		DmSetDevDir = func(dir string) int {
+			calls["DmSetDevDir"] = true
+			expectedDir := "/dev"
+			if dir != expectedDir {
+				t.Fatalf("Wrong libdevmapper call\nExpected: DmSetDevDir(%v)\nReceived: DmSetDevDir(%v)\n", expectedDir, dir)
+			}
+			return 0
+		}
+		LogWithErrnoInit = func() {
+			calls["DmLogWithErrnoInit"] = true
+		}
+		var task1 CDmTask
+		DmTaskCreate = func(taskType int) *CDmTask {
+			calls["DmTaskCreate"] = true
+			taskTypes[fmt.Sprintf("%d", taskType)] = true
+			return &task1
+		}
+		DmTaskSetName = func(task *CDmTask, name string) int {
+			calls["DmTaskSetName"] = true
+			expectedTask := &task1
+			if task != expectedTask {
+				t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetName(%v)\nReceived: DmTaskSetName(%v)\n", expectedTask, task)
+			}
+			// FIXME: use Set.AssertRegexp()
+			if !strings.HasPrefix(name, "docker-") && !strings.HasPrefix(name, "/dev/mapper/docker-") ||
+				!strings.HasSuffix(name, "-pool") && !strings.HasSuffix(name, "-base") {
+				t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetName(%v)\nReceived: DmTaskSetName(%v)\n", "docker-...-pool", name)
+			}
+			return 1
+		}
+		DmTaskRun = func(task *CDmTask) int {
+			calls["DmTaskRun"] = true
+			expectedTask := &task1
+			if task != expectedTask {
+				t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskRun(%v)\nReceived: DmTaskRun(%v)\n", expectedTask, task)
+			}
+			return 1
+		}
+		DmTaskGetInfo = func(task *CDmTask, info *Info) int {
+			calls["DmTaskGetInfo"] = true
+			expectedTask := &task1
+			if task != expectedTask {
+				t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskGetInfo(%v)\nReceived: DmTaskGetInfo(%v)\n", expectedTask, task)
+			}
+			// This will crash if info is not dereferenceable
+			info.Exists = 0
+			return 1
+		}
+		DmTaskSetSector = func(task *CDmTask, sector uint64) int {
+			calls["DmTaskSetSector"] = true
+			expectedTask := &task1
+			if task != expectedTask {
+				t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetSector(%v)\nReceived: DmTaskSetSector(%v)\n", expectedTask, task)
+			}
+			if expectedSector := uint64(0); sector != expectedSector {
+				t.Fatalf("Wrong libdevmapper call to DmTaskSetSector\nExpected: %v\nReceived: %v\n", expectedSector, sector)
+			}
+			return 1
+		}
+		DmTaskSetMessage = func(task *CDmTask, message string) int {
+			calls["DmTaskSetMessage"] = true
+			expectedTask := &task1
+			if task != expectedTask {
+				t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetSector(%v)\nReceived: DmTaskSetSector(%v)\n", expectedTask, task)
+			}
+			taskMessages[message] = true
+			return 1
+		}
+		var (
+			fakeDataLoop       = "/dev/loop42"
+			fakeMetadataLoop   = "/dev/loop43"
+			fakeDataLoopFd     = 42
+			fakeMetadataLoopFd = 43
+		)
+		var attachCount int
+		DmAttachLoopDevice = func(filename string, fd *int) string {
+			calls["DmAttachLoopDevice"] = true
+			if _, exists := devicesAttached[filename]; exists {
+				t.Fatalf("Already attached %s", filename)
+			}
+			devicesAttached[filename] = true
+			// This will crash if fd is not dereferenceable
+			if attachCount == 0 {
+				attachCount++
+				*fd = fakeDataLoopFd
+				return fakeDataLoop
+			} else {
+				*fd = fakeMetadataLoopFd
+				return fakeMetadataLoop
+			}
+		}
+		DmTaskDestroy = func(task *CDmTask) {
+			calls["DmTaskDestroy"] = true
+			expectedTask := &task1
+			if task != expectedTask {
+				t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task)
+			}
+		}
+		fakeBlockSize := int64(4242 * 512)
+		DmGetBlockSize = func(fd uintptr) (int64, sysErrno) {
+			calls["DmGetBlockSize"] = true
+			if expectedFd := uintptr(42); fd != expectedFd {
+				t.Fatalf("Wrong libdevmapper call\nExpected: DmGetBlockSize(%v)\nReceived: DmGetBlockSize(%v)\n", expectedFd, fd)
+			}
+			return fakeBlockSize, 0
+		}
+		DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
+			calls["DmTaskSetTarget"] = true
+			expectedTask := &task1
+			if task != expectedTask {
+				t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task)
+			}
+			if start != 0 {
+				t.Fatalf("Wrong start: %d != %d", start, 0)
+			}
+			if ttype != "thin" && ttype != "thin-pool" {
+				t.Fatalf("Wrong ttype: %s", ttype)
+			}
+			// Quick smoke test
+			if params == "" {
+				t.Fatalf("Params should not be empty")
+			}
+			return 1
+		}
+		fakeCookie := uint(4321)
+		DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int {
+			calls["DmTaskSetCookie"] = true
+			expectedTask := &task1
+			if task != expectedTask {
+				t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task)
+			}
+			if flags != 0 {
+				t.Fatalf("Cookie flags should be 0 (not %x)", flags)
+			}
+			*cookie = fakeCookie
+			return 1
+		}
+		DmUdevWait = func(cookie uint) int {
+			calls["DmUdevWait"] = true
+			if cookie != fakeCookie {
+				t.Fatalf("Wrong cookie: %d != %d", cookie, fakeCookie)
+			}
+			return 1
+		}
+		DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int {
+			if addNode != AddNodeOnCreate {
+				t.Fatalf("Wrong AddNoteType: %v (expected %v)", addNode, AddNodeOnCreate)
+			}
+			calls["DmTaskSetAddNode"] = true
+			return 1
+		}
+		execRun = func(name string, args ...string) error {
+			calls["execRun"] = true
+			if name != "mkfs.ext4" {
+				t.Fatalf("Expected %s to be executed, not %s", "mkfs.ext4", name)
+			}
+			return nil
+		}
+		driver, err := Init(home)
+		if err != nil {
 			t.Fatal(err)
 		}
+		defer func() {
+			if err := driver.Cleanup(); err != nil {
+				t.Fatal(err)
+			}
+		}()
 	}()
+	// Put all tests in a funciton to make sure the garbage collection will
+	// occur.
+
+	// Call GC to cleanup runtime.Finalizers
+	runtime.GC()
+
+	calls.Assert(t,
+		"DmSetDevDir",
+		"DmLogWithErrnoInit",
+		"DmTaskSetName",
+		"DmTaskRun",
+		"DmTaskGetInfo",
+		"DmAttachLoopDevice",
+		"DmTaskDestroy",
+		"execRun",
+		"DmTaskCreate",
+		"DmGetBlockSize",
+		"DmTaskSetTarget",
+		"DmTaskSetCookie",
+		"DmUdevWait",
+		"DmTaskSetSector",
+		"DmTaskSetMessage",
+		"DmTaskSetAddNode",
+	)
+	devicesAttached.Assert(t, path.Join(home, "devicemapper", "data"), path.Join(home, "devicemapper", "metadata"))
+	taskTypes.Assert(t, "0", "6", "17")
+	taskMessages.Assert(t, "create_thin 0", "set_transaction_id 0 1")
+}
 
-	id := "foo"
-	if err := driver.Create(id, ""); err != nil {
-		t.Fatal(err)
+func fakeInit() func(home string) (graphdriver.Driver, error) {
+	oldInit := Init
+	Init = func(home string) (graphdriver.Driver, error) {
+		return &Driver{
+			home: home,
+		}, nil
 	}
-	dir, err := driver.Get(id)
-	if err != nil {
-		t.Fatal(err)
+	return oldInit
+}
+
+func restoreInit(init func(home string) (graphdriver.Driver, error)) {
+	Init = init
+}
+
+func mockAllDevmapper(calls Set) {
+	DmSetDevDir = func(dir string) int {
+		calls["DmSetDevDir"] = true
+		return 0
 	}
-	if st, err := os.Stat(dir); err != nil {
-		t.Fatal(err)
-	} else if !st.IsDir() {
-		t.Fatalf("Get(%V) did not return a directory", id)
+	LogWithErrnoInit = func() {
+		calls["DmLogWithErrnoInit"] = true
+	}
+	DmTaskCreate = func(taskType int) *CDmTask {
+		calls["DmTaskCreate"] = true
+		return &CDmTask{}
+	}
+	DmTaskSetName = func(task *CDmTask, name string) int {
+		calls["DmTaskSetName"] = true
+		return 1
+	}
+	DmTaskRun = func(task *CDmTask) int {
+		calls["DmTaskRun"] = true
+		return 1
+	}
+	DmTaskGetInfo = func(task *CDmTask, info *Info) int {
+		calls["DmTaskGetInfo"] = true
+		return 1
+	}
+	DmTaskSetSector = func(task *CDmTask, sector uint64) int {
+		calls["DmTaskSetSector"] = true
+		return 1
+	}
+	DmTaskSetMessage = func(task *CDmTask, message string) int {
+		calls["DmTaskSetMessage"] = true
+		return 1
+	}
+	DmAttachLoopDevice = func(filename string, fd *int) string {
+		calls["DmAttachLoopDevice"] = true
+		return "/dev/loop42"
+	}
+	DmTaskDestroy = func(task *CDmTask) {
+		calls["DmTaskDestroy"] = true
+	}
+	DmGetBlockSize = func(fd uintptr) (int64, sysErrno) {
+		calls["DmGetBlockSize"] = true
+		return int64(4242 * 512), 0
+	}
+	DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
+		calls["DmTaskSetTarget"] = true
+		return 1
+	}
+	DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int {
+		calls["DmTaskSetCookie"] = true
+		return 1
+	}
+	DmUdevWait = func(cookie uint) int {
+		calls["DmUdevWait"] = true
+		return 1
+	}
+	DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int {
+		calls["DmTaskSetAddNode"] = true
+		return 1
+	}
+	execRun = func(name string, args ...string) error {
+		calls["execRun"] = true
+		return nil
 	}
 }
 
 func TestDriverName(t *testing.T) {
-	d := newDriver(t)
-	defer cleanup(d)
+	denyAllDevmapper()
+	defer denyAllDevmapper()
+
+	oldInit := fakeInit()
+	defer restoreInit(oldInit)
 
+	d := newDriver(t)
 	if d.String() != "devicemapper" {
 		t.Fatalf("Expected driver name to be devicemapper got %s", d.String())
 	}
 }
 
 func TestDriverCreate(t *testing.T) {
-	d := newDriver(t)
-	defer cleanup(d)
-
-	if err := d.Create("1", ""); err != nil {
-		t.Fatal(err)
+	denyAllDevmapper()
+	denyAllSyscall()
+	defer denyAllSyscall()
+	defer denyAllDevmapper()
+
+	calls := make(Set)
+	mockAllDevmapper(calls)
+
+	sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) {
+		calls["sysMount"] = true
+		// FIXME: compare the exact source and target strings (inodes + devname)
+		if expectedSource := "/dev/mapper/docker-"; !strings.HasPrefix(source, expectedSource) {
+			t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedSource, source)
+		}
+		if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) {
+			t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target)
+		}
+		if expectedFstype := "ext4"; fstype != expectedFstype {
+			t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFstype, fstype)
+		}
+		if expectedFlags := uintptr(3236757504); flags != expectedFlags {
+			t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags)
+		}
+		return nil
 	}
+
+	Mounted = func(mnt string) (bool, error) {
+		calls["Mounted"] = true
+		if !strings.HasPrefix(mnt, "/tmp/docker-test-devmapper-") || !strings.HasSuffix(mnt, "/mnt/1") {
+			t.Fatalf("Wrong mounted call\nExpected: Mounted(%v)\nReceived: Mounted(%v)\n", "/tmp/docker-test-devmapper-.../mnt/1", mnt)
+		}
+		return false, nil
+	}
+
+	func() {
+		d := newDriver(t)
+
+		calls.Assert(t,
+			"DmSetDevDir",
+			"DmLogWithErrnoInit",
+			"DmTaskSetName",
+			"DmTaskRun",
+			"DmTaskGetInfo",
+			"DmAttachLoopDevice",
+			"execRun",
+			"DmTaskCreate",
+			"DmGetBlockSize",
+			"DmTaskSetTarget",
+			"DmTaskSetCookie",
+			"DmUdevWait",
+			"DmTaskSetSector",
+			"DmTaskSetMessage",
+			"DmTaskSetAddNode",
+		)
+
+		if err := d.Create("1", ""); err != nil {
+			t.Fatal(err)
+		}
+		calls.Assert(t,
+			"DmTaskCreate",
+			"DmTaskGetInfo",
+			"sysMount",
+			"Mounted",
+			"DmTaskRun",
+			"DmTaskSetTarget",
+			"DmTaskSetSector",
+			"DmTaskSetCookie",
+			"DmUdevWait",
+			"DmTaskSetName",
+			"DmTaskSetMessage",
+			"DmTaskSetAddNode",
+		)
+
+	}()
+
+	runtime.GC()
+
+	calls.Assert(t,
+		"DmTaskDestroy",
+	)
 }
 
 func TestDriverRemove(t *testing.T) {
-	d := newDriver(t)
-	defer cleanup(d)
-
-	if err := d.Create("1", ""); err != nil {
-		t.Fatal(err)
+	denyAllDevmapper()
+	denyAllSyscall()
+	defer denyAllSyscall()
+	defer denyAllDevmapper()
+
+	calls := make(Set)
+	mockAllDevmapper(calls)
+
+	sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) {
+		calls["sysMount"] = true
+		// FIXME: compare the exact source and target strings (inodes + devname)
+		if expectedSource := "/dev/mapper/docker-"; !strings.HasPrefix(source, expectedSource) {
+			t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedSource, source)
+		}
+		if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) {
+			t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target)
+		}
+		if expectedFstype := "ext4"; fstype != expectedFstype {
+			t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFstype, fstype)
+		}
+		if expectedFlags := uintptr(3236757504); flags != expectedFlags {
+			t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags)
+		}
+		return nil
 	}
+	sysUnmount = func(target string, flags int) (err error) {
+		calls["sysUnmount"] = true
+		// FIXME: compare the exact source and target strings (inodes + devname)
+		if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) {
+			t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target)
+		}
+		if expectedFlags := 0; flags != expectedFlags {
+			t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags)
+		}
+		return nil
+	}
+	Mounted = func(mnt string) (bool, error) {
+		calls["Mounted"] = true
+		return false, nil
+	}
+
+	func() {
+		d := newDriver(t)
+
+		calls.Assert(t,
+			"DmSetDevDir",
+			"DmLogWithErrnoInit",
+			"DmTaskSetName",
+			"DmTaskRun",
+			"DmTaskGetInfo",
+			"DmAttachLoopDevice",
+			"execRun",
+			"DmTaskCreate",
+			"DmGetBlockSize",
+			"DmTaskSetTarget",
+			"DmTaskSetCookie",
+			"DmUdevWait",
+			"DmTaskSetSector",
+			"DmTaskSetMessage",
+			"DmTaskSetAddNode",
+		)
+
+		if err := d.Create("1", ""); err != nil {
+			t.Fatal(err)
+		}
 
-	if err := d.Remove("1"); err != nil {
-		t.Fatal(err)
-	}
+		calls.Assert(t,
+			"DmTaskCreate",
+			"DmTaskGetInfo",
+			"sysMount",
+			"Mounted",
+			"DmTaskRun",
+			"DmTaskSetTarget",
+			"DmTaskSetSector",
+			"DmTaskSetCookie",
+			"DmUdevWait",
+			"DmTaskSetName",
+			"DmTaskSetMessage",
+			"DmTaskSetAddNode",
+		)
+
+		Mounted = func(mnt string) (bool, error) {
+			calls["Mounted"] = true
+			return true, nil
+		}
+
+		if err := d.Remove("1"); err != nil {
+			t.Fatal(err)
+		}
+
+		calls.Assert(t,
+			"DmTaskRun",
+			"DmTaskSetSector",
+			"DmTaskSetName",
+			"DmTaskSetMessage",
+			"DmTaskCreate",
+			"DmTaskGetInfo",
+			"Mounted",
+			"sysUnmount",
+		)
+	}()
+	runtime.GC()
+
+	calls.Assert(t,
+		"DmTaskDestroy",
+	)
 }
 
 func TestCleanup(t *testing.T) {
+	t.Skip("FIXME: not a unit test")
 	t.Skip("Unimplemented")
 	d := newDriver(t)
-	defer os.RemoveAll(d.home)
+	defer osRemoveAll(d.home)
 
 	mountPoints := make([]string, 2)
 
@@ -161,6 +712,7 @@ func TestCleanup(t *testing.T) {
 }
 
 func TestNotMounted(t *testing.T) {
+	t.Skip("FIXME: not a unit test")
 	t.Skip("Not implemented")
 	d := newDriver(t)
 	defer cleanup(d)
@@ -179,6 +731,7 @@ func TestNotMounted(t *testing.T) {
 }
 
 func TestMounted(t *testing.T) {
+	t.Skip("FIXME: not a unit test")
 	d := newDriver(t)
 	defer cleanup(d)
 
@@ -199,6 +752,7 @@ func TestMounted(t *testing.T) {
 }
 
 func TestInitCleanedDriver(t *testing.T) {
+	t.Skip("FIXME: not a unit test")
 	d := newDriver(t)
 
 	if err := d.Create("1", ""); err != nil {
@@ -225,6 +779,7 @@ func TestInitCleanedDriver(t *testing.T) {
 }
 
 func TestMountMountedDriver(t *testing.T) {
+	t.Skip("FIXME: not a unit test")
 	d := newDriver(t)
 	defer cleanup(d)
 
@@ -243,6 +798,7 @@ func TestMountMountedDriver(t *testing.T) {
 }
 
 func TestGetReturnsValidDevice(t *testing.T) {
+	t.Skip("FIXME: not a unit test")
 	d := newDriver(t)
 	defer cleanup(d)
 
@@ -268,6 +824,7 @@ func TestGetReturnsValidDevice(t *testing.T) {
 }
 
 func TestDriverGetSize(t *testing.T) {
+	t.Skip("FIXME: not a unit test")
 	t.Skipf("Size is currently not implemented")
 
 	d := newDriver(t)
@@ -284,7 +841,7 @@ func TestDriverGetSize(t *testing.T) {
 
 	size := int64(1024)
 
-	f, err := os.Create(path.Join(mountPoint, "test_file"))
+	f, err := osCreate(path.Join(mountPoint, "test_file"))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -301,3 +858,15 @@ func TestDriverGetSize(t *testing.T) {
 	// 	t.Fatalf("Expected size %d got %d", size, diffSize)
 	// }
 }
+
+func assertMap(t *testing.T, m map[string]bool, keys ...string) {
+	for _, key := range keys {
+		if _, exists := m[key]; !exists {
+			t.Fatalf("Key not set: %s", key)
+		}
+		delete(m, key)
+	}
+	if len(m) != 0 {
+		t.Fatalf("Unexpected keys: %v", m)
+	}
+}

+ 6 - 8
graphdriver/devmapper/mount.go

@@ -1,27 +1,25 @@
 package devmapper
 
 import (
-	"os"
 	"path/filepath"
-	"syscall"
 )
 
 // FIXME: this is copy-pasted from the aufs driver.
 // It should be moved into the core.
 
-func Mounted(mountpoint string) (bool, error) {
-	mntpoint, err := os.Stat(mountpoint)
+var Mounted = func(mountpoint string) (bool, error) {
+	mntpoint, err := osStat(mountpoint)
 	if err != nil {
-		if os.IsNotExist(err) {
+		if osIsNotExist(err) {
 			return false, nil
 		}
 		return false, err
 	}
-	parent, err := os.Stat(filepath.Join(mountpoint, ".."))
+	parent, err := osStat(filepath.Join(mountpoint, ".."))
 	if err != nil {
 		return false, err
 	}
-	mntpointSt := mntpoint.Sys().(*syscall.Stat_t)
-	parentSt := parent.Sys().(*syscall.Stat_t)
+	mntpointSt := toSysStatT(mntpoint.Sys())
+	parentSt := toSysStatT(parent.Sys())
 	return mntpointSt.Dev != parentSt.Dev, nil
 }

+ 50 - 0
graphdriver/devmapper/sys.go

@@ -0,0 +1,50 @@
+package devmapper
+
+import (
+	"os"
+	"os/exec"
+	"syscall"
+)
+
+type (
+	sysStatT syscall.Stat_t
+	sysErrno syscall.Errno
+
+	osFile struct{ *os.File }
+)
+
+var (
+	sysMount       = syscall.Mount
+	sysUnmount     = syscall.Unmount
+	sysCloseOnExec = syscall.CloseOnExec
+	sysSyscall     = syscall.Syscall
+
+	osOpenFile   = os.OpenFile
+	osNewFile    = os.NewFile
+	osCreate     = os.Create
+	osStat       = os.Stat
+	osIsNotExist = os.IsNotExist
+	osIsExist    = os.IsExist
+	osMkdirAll   = os.MkdirAll
+	osRemoveAll  = os.RemoveAll
+	osRename     = os.Rename
+	osReadlink   = os.Readlink
+
+	execRun = func(name string, args ...string) error {
+		return exec.Command(name, args...).Run()
+	}
+)
+
+const (
+	sysMsMgcVal = syscall.MS_MGC_VAL
+	sysMsRdOnly = syscall.MS_RDONLY
+	sysEInval   = syscall.EINVAL
+	sysSysIoctl = syscall.SYS_IOCTL
+
+	osORdWr   = os.O_RDWR
+	osOCreate = os.O_CREATE
+)
+
+func toSysStatT(i interface{}) *sysStatT {
+	return (*sysStatT)(i.(*syscall.Stat_t))
+}