summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Unterwurzacher2019-01-01 22:04:36 +0100
committerJakob Unterwurzacher2019-01-01 22:27:10 +0100
commite276e255dc7d88099f35c890b704ce64117f731e (patch)
tree99c9cbe2ac8a0aaafb7ba92921dcb52c6ee12bcc
parent10de105c13e4ef512fe83b8c1074fc453f3e70ff (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.go178
-rw-r--r--tests/test_helpers/mount_unmount.go189
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
+}