go-imap
go-imap copied to clipboard
client: Dovecot startup failure hangs test
If a bad config file is provided to Dovecot, the net.Pipe will not be closed and the test hangs.
For some reason cmd.Wait() in a goroutine hangs after doveadm exits with an error... Seems like cmd.Process.Wait() works as expected though.
Hm, it doesn't seem like passing an *os.File as stdin/stdout/stderr helps…
diff --git a/imapclient/dovecot_test.go b/imapclient/dovecot_test.go
index 10386004ec52..c9599eb0d3b2 100644
--- a/imapclient/dovecot_test.go
+++ b/imapclient/dovecot_test.go
@@ -7,13 +7,16 @@ import (
"os/exec"
"path/filepath"
"testing"
+ "syscall"
+
+ "log"
)
func newDovecotClientServerPair(t *testing.T) (net.Conn, io.Closer) {
tempDir := t.TempDir()
cfgFilename := filepath.Join(tempDir, "dovecot.conf")
- cfg := `log_path = "` + tempDir + `/dovecot.log"
+ cfg := `foolog_path = "` + tempDir + `/dovecot.log"
ssl = no
mail_home = "` + tempDir + `/%u"
mail_location = maildir:~/Mail
@@ -36,29 +39,71 @@ plugin {
t.Fatalf("failed to write Dovecot config: %v", err)
}
- clientConn, serverConn := net.Pipe()
+ // Use an *os.File when spawning the child process so that cmd.Wait doesn't
+ // wait for the pipe copy to complete
+ clientSocketFile, serverSocketFile, err := createSocketPair()
+ if err != nil {
+ t.Fatalf("failed to create socket pair: %v", err)
+ }
cmd := exec.Command("doveadm", "-c", cfgFilename, "exec", "imap")
cmd.Env = []string{"USER=" + testUsername, "PATH=" + os.Getenv("PATH")}
cmd.Dir = tempDir
- cmd.Stdin = serverConn
- cmd.Stdout = serverConn
+ cmd.Stdin = serverSocketFile
+ cmd.Stdout = serverSocketFile
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
t.Fatalf("failed to start Dovecot: %v", err)
}
- return clientConn, &dovecotServer{cmd, serverConn}
+ cmdDone := make(chan struct{})
+ go func() {
+ defer close(cmdDone)
+ log.Print("BBB")
+ if err := cmd.Wait(); err != nil {
+ t.Fatalf("Dovecot failed: %v", err)
+ }
+ log.Print("CCC")
+ }()
+ t.Cleanup(func() {
+ <-cmdDone
+ })
+
+ log.Print("AAA")
+ return fileConn{clientSocketFile}, &dovecotServer{serverSocketFile}
}
type dovecotServer struct {
- cmd *exec.Cmd
- conn net.Conn
+ io.Closer
}
-func (srv *dovecotServer) Close() error {
- if err := srv.conn.Close(); err != nil {
- return err
+func createSocketPair() (*os.File, *os.File, error) {
+ fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM, 0)
+ if err != nil {
+ return nil, nil, err
}
- return srv.cmd.Wait()
+ return os.NewFile(uintptr(fds[0]), "socketpair"), os.NewFile(uintptr(fds[1]), "socketpair"), nil
+}
+
+// fileConn behaves like net.FileConn, expect it closes the file.
+type fileConn struct {
+ *os.File
+}
+
+func (conn fileConn) LocalAddr() net.Addr {
+ return fileConnAddr{}
+}
+
+func (conn fileConn) RemoteAddr() net.Addr {
+ return fileConnAddr{}
+}
+
+type fileConnAddr struct{}
+
+func (fileConnAddr) Network() string {
+ return "file"
+}
+
+func (fileConnAddr) String() string {
+ return "file"
}