// +build linux

package devmapper

import (
	"fmt"
	"github.com/dotcloud/docker/graphdriver"
	"io/ioutil"
	"path"
	"runtime"
	"strings"
	"syscall"
	"testing"
)

func init() {
	// Reduce the size the the base fs and loopback for the tests
	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")
	}
	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")
	}
	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 {
	dir, err := ioutil.TempDir("", "docker-test-devmapper-")
	if err != nil {
		t.Fatal(err)
	}
	return dir
}

func newDriver(t *testing.T) *Driver {
	home := mkTestDirectory(t)
	d, err := Init(home)
	if err != nil {
		t.Fatal(err)
	}
	return d.(*Driver)
}

func cleanup(d *Driver) {
	d.Cleanup()
	osRemoveAll(d.home)
}

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)
	}
	if len(r) != 0 {
		t.Fatalf("Unexpected keys: %v", r)
	}
}

func TestInit(t *testing.T) {
	var (
		calls        = 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
		}
		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)
			}
		}
		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",
		"DmTaskDestroy",
		"execRun",
		"DmTaskCreate",
		"DmTaskSetTarget",
		"DmTaskSetCookie",
		"DmUdevWait",
		"DmTaskSetSector",
		"DmTaskSetMessage",
		"DmTaskSetAddNode",
	)
	taskTypes.Assert(t, "0", "6", "17")
	taskMessages.Assert(t, "create_thin 0", "set_transaction_id 0 1")
}

func fakeInit() func(home string) (graphdriver.Driver, error) {
	oldInit := Init
	Init = func(home string) (graphdriver.Driver, error) {
		return &Driver{
			home: home,
		}, nil
	}
	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
	}
	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
	}
	DmTaskDestroy = func(task *CDmTask) {
		calls["DmTaskDestroy"] = true
	}
	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) {
	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) {
	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
	}

	sysSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
		calls["sysSyscall"] = true
		if trap != sysSysIoctl {
			t.Fatalf("Unexpected syscall. Expecting SYS_IOCTL, received: %d", trap)
		}
		switch a2 {
		case LoopSetFd:
			calls["ioctl.loopsetfd"] = true
		case LoopCtlGetFree:
			calls["ioctl.loopctlgetfree"] = true
		case LoopGetStatus64:
			calls["ioctl.loopgetstatus"] = true
		case LoopSetStatus64:
			calls["ioctl.loopsetstatus"] = true
		case LoopClrFd:
			calls["ioctl.loopclrfd"] = true
		case LoopSetCapacity:
			calls["ioctl.loopsetcapacity"] = true
		case BlkGetSize64:
			calls["ioctl.blkgetsize"] = true
		default:
			t.Fatalf("Unexpected IOCTL. Received %d", a2)
		}
		return 0, 0, 0
	}

	func() {
		d := newDriver(t)

		calls.Assert(t,
			"DmSetDevDir",
			"DmLogWithErrnoInit",
			"DmTaskSetName",
			"DmTaskRun",
			"DmTaskGetInfo",
			"execRun",
			"DmTaskCreate",
			"DmTaskSetTarget",
			"DmTaskSetCookie",
			"DmUdevWait",
			"DmTaskSetSector",
			"DmTaskSetMessage",
			"DmTaskSetAddNode",
			"sysSyscall",
			"ioctl.blkgetsize",
			"ioctl.loopsetfd",
			"ioctl.loopsetstatus",
		)

		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) {
	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
	}

	sysSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
		calls["sysSyscall"] = true
		if trap != sysSysIoctl {
			t.Fatalf("Unexpected syscall. Expecting SYS_IOCTL, received: %d", trap)
		}
		switch a2 {
		case LoopSetFd:
			calls["ioctl.loopsetfd"] = true
		case LoopCtlGetFree:
			calls["ioctl.loopctlgetfree"] = true
		case LoopGetStatus64:
			calls["ioctl.loopgetstatus"] = true
		case LoopSetStatus64:
			calls["ioctl.loopsetstatus"] = true
		case LoopClrFd:
			calls["ioctl.loopclrfd"] = true
		case LoopSetCapacity:
			calls["ioctl.loopsetcapacity"] = true
		case BlkGetSize64:
			calls["ioctl.blkgetsize"] = true
		default:
			t.Fatalf("Unexpected IOCTL. Received %d", a2)
		}
		return 0, 0, 0
	}

	func() {
		d := newDriver(t)

		calls.Assert(t,
			"DmSetDevDir",
			"DmLogWithErrnoInit",
			"DmTaskSetName",
			"DmTaskRun",
			"DmTaskGetInfo",
			"execRun",
			"DmTaskCreate",
			"DmTaskSetTarget",
			"DmTaskSetCookie",
			"DmUdevWait",
			"DmTaskSetSector",
			"DmTaskSetMessage",
			"DmTaskSetAddNode",
			"sysSyscall",
			"ioctl.blkgetsize",
			"ioctl.loopsetfd",
			"ioctl.loopsetstatus",
		)

		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",
		)

		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 osRemoveAll(d.home)

	mountPoints := make([]string, 2)

	if err := d.Create("1", ""); err != nil {
		t.Fatal(err)
	}
	// Mount the id
	p, err := d.Get("1")
	if err != nil {
		t.Fatal(err)
	}
	mountPoints[0] = p

	if err := d.Create("2", "1"); err != nil {
		t.Fatal(err)
	}

	p, err = d.Get("2")
	if err != nil {
		t.Fatal(err)
	}
	mountPoints[1] = p

	// Ensure that all the mount points are currently mounted
	for _, p := range mountPoints {
		if mounted, err := Mounted(p); err != nil {
			t.Fatal(err)
		} else if !mounted {
			t.Fatalf("Expected %s to be mounted", p)
		}
	}

	// Ensure that devices are active
	for _, p := range []string{"1", "2"} {
		if !d.HasActivatedDevice(p) {
			t.Fatalf("Expected %s to have an active device", p)
		}
	}

	if err := d.Cleanup(); err != nil {
		t.Fatal(err)
	}

	// Ensure that all the mount points are no longer mounted
	for _, p := range mountPoints {
		if mounted, err := Mounted(p); err != nil {
			t.Fatal(err)
		} else if mounted {
			t.Fatalf("Expected %s to not be mounted", p)
		}
	}

	// Ensure that devices are no longer activated
	for _, p := range []string{"1", "2"} {
		if d.HasActivatedDevice(p) {
			t.Fatalf("Expected %s not be an active device", p)
		}
	}
}

func TestNotMounted(t *testing.T) {
	t.Skip("FIXME: not a unit test")
	t.Skip("Not implemented")
	d := newDriver(t)
	defer cleanup(d)

	if err := d.Create("1", ""); err != nil {
		t.Fatal(err)
	}

	mounted, err := Mounted(path.Join(d.home, "mnt", "1"))
	if err != nil {
		t.Fatal(err)
	}
	if mounted {
		t.Fatal("Id 1 should not be mounted")
	}
}

func TestMounted(t *testing.T) {
	t.Skip("FIXME: not a unit test")
	d := newDriver(t)
	defer cleanup(d)

	if err := d.Create("1", ""); err != nil {
		t.Fatal(err)
	}
	if _, err := d.Get("1"); err != nil {
		t.Fatal(err)
	}

	mounted, err := Mounted(path.Join(d.home, "mnt", "1"))
	if err != nil {
		t.Fatal(err)
	}
	if !mounted {
		t.Fatal("Id 1 should be mounted")
	}
}

func TestInitCleanedDriver(t *testing.T) {
	t.Skip("FIXME: not a unit test")
	d := newDriver(t)

	if err := d.Create("1", ""); err != nil {
		t.Fatal(err)
	}
	if _, err := d.Get("1"); err != nil {
		t.Fatal(err)
	}

	if err := d.Cleanup(); err != nil {
		t.Fatal(err)
	}

	driver, err := Init(d.home)
	if err != nil {
		t.Fatal(err)
	}
	d = driver.(*Driver)
	defer cleanup(d)

	if _, err := d.Get("1"); err != nil {
		t.Fatal(err)
	}
}

func TestMountMountedDriver(t *testing.T) {
	t.Skip("FIXME: not a unit test")
	d := newDriver(t)
	defer cleanup(d)

	if err := d.Create("1", ""); err != nil {
		t.Fatal(err)
	}

	// Perform get on same id to ensure that it will
	// not be mounted twice
	if _, err := d.Get("1"); err != nil {
		t.Fatal(err)
	}
	if _, err := d.Get("1"); err != nil {
		t.Fatal(err)
	}
}

func TestGetReturnsValidDevice(t *testing.T) {
	t.Skip("FIXME: not a unit test")
	d := newDriver(t)
	defer cleanup(d)

	if err := d.Create("1", ""); err != nil {
		t.Fatal(err)
	}

	if !d.HasDevice("1") {
		t.Fatalf("Expected id 1 to be in device set")
	}

	if _, err := d.Get("1"); err != nil {
		t.Fatal(err)
	}

	if !d.HasActivatedDevice("1") {
		t.Fatalf("Expected id 1 to be activated")
	}

	if !d.HasInitializedDevice("1") {
		t.Fatalf("Expected id 1 to be initialized")
	}
}

func TestDriverGetSize(t *testing.T) {
	t.Skip("FIXME: not a unit test")
	t.Skipf("Size is currently not implemented")

	d := newDriver(t)
	defer cleanup(d)

	if err := d.Create("1", ""); err != nil {
		t.Fatal(err)
	}

	mountPoint, err := d.Get("1")
	if err != nil {
		t.Fatal(err)
	}

	size := int64(1024)

	f, err := osCreate(path.Join(mountPoint, "test_file"))
	if err != nil {
		t.Fatal(err)
	}
	if err := f.Truncate(size); err != nil {
		t.Fatal(err)
	}
	f.Close()

	// diffSize, err := d.DiffSize("1")
	// if err != nil {
	// 	t.Fatal(err)
	// }
	// if diffSize != size {
	// 	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)
	}
}