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.
This commit is contained in:
parent
10de105c13
commit
e276e255dc
@ -6,15 +6,12 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -46,16 +43,6 @@ var DefaultPlainDir string
|
|||||||
// DefaultCipherDir is TmpDir + "/default-cipher"
|
// DefaultCipherDir is TmpDir + "/default-cipher"
|
||||||
var DefaultCipherDir string
|
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.
|
// 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
|
// 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.
|
// 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
|
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"
|
// Md5fn returns an md5 string for file "filename"
|
||||||
func Md5fn(filename string) string {
|
func Md5fn(filename string) string {
|
||||||
buf, err := ioutil.ReadFile(filename)
|
buf, err := ioutil.ReadFile(filename)
|
||||||
@ -504,33 +356,3 @@ func ExtractCmdExitCode(err error) int {
|
|||||||
log.Panicf("could not decode error %#v", err)
|
log.Panicf("could not decode error %#v", err)
|
||||||
return 0
|
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
|
|
||||||
}
|
|
||||||
|
189
tests/test_helpers/mount_unmount.go
Normal file
189
tests/test_helpers/mount_unmount.go
Normal file
@ -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
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user