diff options
| author | Jakob Unterwurzacher | 2019-01-01 22:04:36 +0100 | 
|---|---|---|
| committer | Jakob Unterwurzacher | 2019-01-01 22:27:10 +0100 | 
| commit | e276e255dc7d88099f35c890b704ce64117f731e (patch) | |
| tree | 99c9cbe2ac8a0aaafb7ba92921dcb52c6ee12bcc | |
| parent | 10de105c13e4ef512fe83b8c1074fc453f3e70ff (diff) | |
tests: split mount_unmount.go from helpers.go
With the FD leak logic, the mount/unmount functions have
become complex enough to give them their own file.
| -rw-r--r-- | tests/test_helpers/helpers.go | 178 | ||||
| -rw-r--r-- | tests/test_helpers/mount_unmount.go | 189 | 
2 files changed, 189 insertions, 178 deletions
| diff --git a/tests/test_helpers/helpers.go b/tests/test_helpers/helpers.go index 1982b96..c6a4d6e 100644 --- a/tests/test_helpers/helpers.go +++ b/tests/test_helpers/helpers.go @@ -6,15 +6,12 @@ import (  	"encoding/hex"  	"encoding/json"  	"fmt" -	"io"  	"io/ioutil"  	"log"  	"net"  	"os"  	"os/exec" -	"os/signal"  	"path/filepath" -	"runtime"  	"syscall"  	"testing"  	"time" @@ -46,16 +43,6 @@ var DefaultPlainDir string  // DefaultCipherDir is TmpDir + "/default-cipher"  var DefaultCipherDir string -type mountInfo struct { -	// PID of the running gocryptfs process. Set by Mount(). -	pid int -	// List of open FDs of the running gocrypts process. Set by Mount(). -	fds []string -} - -// Indexed by mountpoint -var MountInfo map[string]mountInfo -  // SwitchTMPDIR changes TMPDIR and hence the directory the test are performed in.  // This is used when you want to perform tests on a special filesystem. The  // xattr tests cannot run on tmpfs and use /var/tmp instead of /tmp. @@ -165,141 +152,6 @@ func InitFS(t *testing.T, extraArgs ...string) string {  	return dir  } -// Mount CIPHERDIR "c" on PLAINDIR "p" -// Creates "p" if it does not exist. -func Mount(c string, p string, showOutput bool, extraArgs ...string) error { -	args := []string{"-q", "-wpanic", "-nosyslog", "-fg", fmt.Sprintf("-notifypid=%d", os.Getpid())} -	args = append(args, extraArgs...) -	//args = append(args, "-fusedebug") -	//args = append(args, "-d") -	args = append(args, c, p) - -	if _, err := os.Stat(p); err != nil { -		err = os.Mkdir(p, 0777) -		if err != nil { -			return err -		} -	} - -	cmd := exec.Command(GocryptfsBinary, args...) -	if showOutput { -		// The Go test logic waits for our stdout to close, and when we share -		// it with the subprocess, it will wait for it to close it as well. -		// Use an intermediate pipe so the tests do not hang when unmouting -		// fails. -		pr, pw, err := os.Pipe() -		if err != nil { -			return err -		} -		// We can close the fd after cmd.Run() has executed -		defer pw.Close() -		cmd.Stderr = pw -		cmd.Stdout = pw -		go func() { -			io.Copy(os.Stdout, pr) -			pr.Close() -		}() -	} - -	// Two things can happen: -	// 1) The mount fails and the process exits -	// 2) The mount succeeds and the process sends us USR1 -	chanExit := make(chan error, 1) -	chanUsr1 := make(chan os.Signal, 1) -	signal.Notify(chanUsr1, syscall.SIGUSR1) - -	// Start the process and save the PID -	err := cmd.Start() -	if err != nil { -		return err -	} -	pid := cmd.Process.Pid - -	// Wait for exit or usr1 -	go func() { -		chanExit <- cmd.Wait() -	}() -	select { -	case err := <-chanExit: -		return err -	case <-chanUsr1: -		// noop -	case <-time.After(1 * time.Second): -		log.Panicf("Timeout waiting for process %d", pid) -	} - -	// Save PID and open FDs -	MountInfo[p] = mountInfo{pid, ListFds(pid)} -	return nil -} - -// MountOrExit calls Mount() and exits on failure. -func MountOrExit(c string, p string, extraArgs ...string) { -	err := Mount(c, p, true, extraArgs...) -	if err != nil { -		fmt.Printf("mount failed: %v\n", err) -		os.Exit(1) -	} -} - -// MountOrFatal calls Mount() and calls t.Fatal() on failure. -func MountOrFatal(t *testing.T, c string, p string, extraArgs ...string) { -	err := Mount(c, p, true, extraArgs...) -	if err != nil { -		t.Fatal(fmt.Errorf("mount failed: %v", err)) -	} -} - -// UnmountPanic tries to umount "dir" and panics on error. -func UnmountPanic(dir string) { -	err := UnmountErr(dir) -	if err != nil { -		fmt.Printf("UnmountPanic: %v. Running lsof %s\n", err, dir) -		cmd := exec.Command("lsof", dir) -		cmd.Stdout = os.Stdout -		cmd.Stderr = os.Stderr -		cmd.Run() -		panic("UnmountPanic: unmount failed: " + err.Error()) -	} -} - -// UnmountErr tries to unmount "dir", retrying 10 times, and returns the -// resulting error. -func UnmountErr(dir string) (err error) { -	var fdsNow []string -	pid := MountInfo[dir].pid -	fds := MountInfo[dir].fds -	if pid <= 0 { -		fmt.Printf("UnmountErr: %q was not found in MountInfo, cannot check for FD leaks\n", dir) -	} - -	max := 10 -	// When a new filesystem is mounted, Gnome tries to read files like -	// .xdg-volume-info, autorun.inf, .Trash. -	// If we try to unmount before Gnome is done, the unmount fails with -	// "Device or resource busy", causing spurious test failures. -	// Retry a few times to hide that problem. -	for i := 1; i <= max; i++ { -		if pid > 0 { -			fdsNow = ListFds(pid) -		} -		cmd := exec.Command(UnmountScript, "-u", dir) -		cmd.Stdout = os.Stdout -		cmd.Stderr = os.Stderr -		err = cmd.Run() -		if err == nil { -			if pid > 0 && len(fdsNow) > len(fds) { -				fmt.Printf("FD leak? Details:\nold=%v \nnew=%v\n", fds, fdsNow) -			} -			return nil -		} -		code := ExtractCmdExitCode(err) -		fmt.Printf("UnmountErr: got exit code %d, retrying (%d/%d)\n", code, i, max) -		time.Sleep(100 * time.Millisecond) -	} -	return err -} -  // Md5fn returns an md5 string for file "filename"  func Md5fn(filename string) string {  	buf, err := ioutil.ReadFile(filename) @@ -504,33 +356,3 @@ func ExtractCmdExitCode(err error) int {  	log.Panicf("could not decode error %#v", err)  	return 0  } - -// ListFds lists the open file descriptors for process "pid". Pass pid=0 for -// ourselves. -func ListFds(pid int) []string { -	// We need /proc to get the list of fds for other processes. Only exists -	// on Linux. -	if runtime.GOOS != "linux" && pid > 0 { -		return nil -	} -	// Both Linux and MacOS have /dev/fd -	dir := "/dev/fd" -	if pid > 0 { -		dir = fmt.Sprintf("/proc/%d/fd", pid) -	} -	f, err := os.Open(dir) -	if err != nil { -		log.Panic(err) -	} -	defer f.Close() -	names, err := f.Readdirnames(0) -	if err != nil { -		log.Panic(err) -	} -	for i, n := range names { -		// Note: Readdirnames filters "." and ".." -		target, _ := os.Readlink(dir + "/" + n) -		names[i] = n + "=" + target -	} -	return names -} diff --git a/tests/test_helpers/mount_unmount.go b/tests/test_helpers/mount_unmount.go new file mode 100644 index 0000000..5035c2b --- /dev/null +++ b/tests/test_helpers/mount_unmount.go @@ -0,0 +1,189 @@ +package test_helpers + +import ( +	"fmt" +	"io" +	"log" +	"os" +	"os/exec" +	"os/signal" +	"runtime" +	"syscall" +	"testing" +	"time" +) + +// Indexed by mountpoint. Initialized in doInit(). +var MountInfo map[string]mountInfo + +type mountInfo struct { +	// PID of the running gocryptfs process. Set by Mount(). +	pid int +	// List of open FDs of the running gocrypts process. Set by Mount(). +	fds []string +} + +// Mount CIPHERDIR "c" on PLAINDIR "p" +// Creates "p" if it does not exist. +func Mount(c string, p string, showOutput bool, extraArgs ...string) error { +	args := []string{"-q", "-wpanic", "-nosyslog", "-fg", fmt.Sprintf("-notifypid=%d", os.Getpid())} +	args = append(args, extraArgs...) +	//args = append(args, "-fusedebug") +	//args = append(args, "-d") +	args = append(args, c, p) + +	if _, err := os.Stat(p); err != nil { +		err = os.Mkdir(p, 0777) +		if err != nil { +			return err +		} +	} + +	cmd := exec.Command(GocryptfsBinary, args...) +	if showOutput { +		// The Go test logic waits for our stdout to close, and when we share +		// it with the subprocess, it will wait for it to close it as well. +		// Use an intermediate pipe so the tests do not hang when unmouting +		// fails. +		pr, pw, err := os.Pipe() +		if err != nil { +			return err +		} +		// We can close the fd after cmd.Run() has executed +		defer pw.Close() +		cmd.Stderr = pw +		cmd.Stdout = pw +		go func() { +			io.Copy(os.Stdout, pr) +			pr.Close() +		}() +	} + +	// Two things can happen: +	// 1) The mount fails and the process exits +	// 2) The mount succeeds and the process sends us USR1 +	chanExit := make(chan error, 1) +	chanUsr1 := make(chan os.Signal, 1) +	signal.Notify(chanUsr1, syscall.SIGUSR1) + +	// Start the process and save the PID +	err := cmd.Start() +	if err != nil { +		return err +	} +	pid := cmd.Process.Pid + +	// Wait for exit or usr1 +	go func() { +		chanExit <- cmd.Wait() +	}() +	select { +	case err := <-chanExit: +		return err +	case <-chanUsr1: +		// noop +	case <-time.After(1 * time.Second): +		log.Panicf("Timeout waiting for process %d", pid) +	} + +	// Save PID and open FDs +	MountInfo[p] = mountInfo{pid, ListFds(pid)} +	return nil +} + +// MountOrExit calls Mount() and exits on failure. +func MountOrExit(c string, p string, extraArgs ...string) { +	err := Mount(c, p, true, extraArgs...) +	if err != nil { +		fmt.Printf("mount failed: %v\n", err) +		os.Exit(1) +	} +} + +// MountOrFatal calls Mount() and calls t.Fatal() on failure. +func MountOrFatal(t *testing.T, c string, p string, extraArgs ...string) { +	err := Mount(c, p, true, extraArgs...) +	if err != nil { +		t.Fatal(fmt.Errorf("mount failed: %v", err)) +	} +} + +// UnmountPanic tries to umount "dir" and panics on error. +func UnmountPanic(dir string) { +	err := UnmountErr(dir) +	if err != nil { +		fmt.Printf("UnmountPanic: %v. Running lsof %s\n", err, dir) +		cmd := exec.Command("lsof", dir) +		cmd.Stdout = os.Stdout +		cmd.Stderr = os.Stderr +		cmd.Run() +		panic("UnmountPanic: unmount failed: " + err.Error()) +	} +} + +// UnmountErr tries to unmount "dir", retrying 10 times, and returns the +// resulting error. +func UnmountErr(dir string) (err error) { +	var fdsNow []string +	pid := MountInfo[dir].pid +	fds := MountInfo[dir].fds +	if pid <= 0 { +		fmt.Printf("UnmountErr: %q was not found in MountInfo, cannot check for FD leaks\n", dir) +	} + +	max := 10 +	// When a new filesystem is mounted, Gnome tries to read files like +	// .xdg-volume-info, autorun.inf, .Trash. +	// If we try to unmount before Gnome is done, the unmount fails with +	// "Device or resource busy", causing spurious test failures. +	// Retry a few times to hide that problem. +	for i := 1; i <= max; i++ { +		if pid > 0 { +			fdsNow = ListFds(pid) +		} +		cmd := exec.Command(UnmountScript, "-u", dir) +		cmd.Stdout = os.Stdout +		cmd.Stderr = os.Stderr +		err = cmd.Run() +		if err == nil { +			if pid > 0 && len(fdsNow) > len(fds) { +				fmt.Printf("FD leak? Details:\nold=%v \nnew=%v\n", fds, fdsNow) +			} +			return nil +		} +		code := ExtractCmdExitCode(err) +		fmt.Printf("UnmountErr: got exit code %d, retrying (%d/%d)\n", code, i, max) +		time.Sleep(100 * time.Millisecond) +	} +	return err +} + +// ListFds lists the open file descriptors for process "pid". Pass pid=0 for +// ourselves. +func ListFds(pid int) []string { +	// We need /proc to get the list of fds for other processes. Only exists +	// on Linux. +	if runtime.GOOS != "linux" && pid > 0 { +		return nil +	} +	// Both Linux and MacOS have /dev/fd +	dir := "/dev/fd" +	if pid > 0 { +		dir = fmt.Sprintf("/proc/%d/fd", pid) +	} +	f, err := os.Open(dir) +	if err != nil { +		log.Panic(err) +	} +	defer f.Close() +	names, err := f.Readdirnames(0) +	if err != nil { +		log.Panic(err) +	} +	for i, n := range names { +		// Note: Readdirnames filters "." and ".." +		target, _ := os.Readlink(dir + "/" + n) +		names[i] = n + "=" + target +	} +	return names +} | 
