util/io: add RedirectStderr #214
17
util/io/redirect.go
Normal file
17
util/io/redirect.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package io
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var osStderrMu sync.Mutex
|
||||||
|
|
||||||
|
var OrigStderr = func() *os.File {
|
||||||
|
fd, err := dupFD(os.Stderr.Fd())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.NewFile(fd, os.Stderr.Name())
|
||||||
|
}()
|
40
util/io/redirect_test.go
Normal file
40
util/io/redirect_test.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package io
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrorPattern = regexp.MustCompile(`"error"`)
|
||||||
|
|
||||||
|
func TestRedirect(t *testing.T) {
|
||||||
|
r, w, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan string)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
_, _ = io.Copy(buf, r)
|
||||||
|
ch <- buf.String()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err = RedirectStderr(w); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Stderr.Write([]byte(`test redirect`))
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
r.Close()
|
||||||
|
str := <-ch
|
||||||
|
if ErrorPattern.MatchString(str) {
|
||||||
|
t.Fatal(fmt.Errorf(str))
|
||||||
|
}
|
||||||
|
}
|
48
util/io/redirect_unix.go
Normal file
48
util/io/redirect_unix.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package io
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dupFD is used to initialize OrigStderr (see stderr_redirect.go).
|
||||||
|
func dupFD(fd uintptr) (uintptr, error) {
|
||||||
|
// Warning: failing to set FD_CLOEXEC causes the duplicated file descriptor
|
||||||
|
// to leak into subprocesses created by exec.Command. If the file descriptor
|
||||||
|
// is a pipe, these subprocesses will hold the pipe open (i.e., prevent
|
||||||
|
// EOF), potentially beyond the lifetime of this process.
|
||||||
|
//
|
||||||
|
// This can break go test's timeouts. go test usually spawns a test process
|
||||||
|
// with its stdin and stderr streams hooked up to pipes; if the test process
|
||||||
|
// times out, it sends a SIGKILL and attempts to read stdin and stderr to
|
||||||
|
// completion. If the test process has itself spawned long-lived
|
||||||
|
// subprocesses that hold references to the stdin or stderr pipes, go test
|
||||||
|
// will hang until the subprocesses exit, rather defeating the purpose of
|
||||||
|
// a timeout.
|
||||||
|
nfd, err := unix.FcntlInt(fd, unix.F_DUPFD_CLOEXEC, 0)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uintptr(nfd), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedirectStderr is used to redirect internal writes to fd 2 to the
|
||||||
|
// specified file. This is needed to ensure that harcoded writes to fd
|
||||||
|
// 2 by e.g. the Go runtime are redirected to a log file of our
|
||||||
|
// choosing.
|
||||||
|
//
|
||||||
|
// We also override os.Stderr for those other parts of Go which use
|
||||||
|
// that and not fd 2 directly.
|
||||||
|
func RedirectStderr(f *os.File) error {
|
||||||
|
osStderrMu.Lock()
|
||||||
|
defer osStderrMu.Unlock()
|
||||||
|
if err := unix.Dup2(int(f.Fd()), unix.Stderr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
os.Stderr = f
|
||||||
|
return nil
|
||||||
|
}
|
32
util/io/redirect_windows.go
Normal file
32
util/io/redirect_windows.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package io
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dupFD is used to initialize OrigStderr (see stderr_redirect.go).
|
||||||
|
func dupFD(fd uintptr) (uintptr, error) {
|
||||||
|
// Adapted from https://github.com/golang/go/blob/go1.8/src/syscall/exec_windows.go#L303.
|
||||||
|
p := windows.CurrentProcess()
|
||||||
|
var h windows.Handle
|
||||||
|
return uintptr(h), windows.DuplicateHandle(p, windows.Handle(fd), p, &h, 0, true, windows.DUPLICATE_SAME_ACCESS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedirectStderr is used to redirect internal writes to the error
|
||||||
|
// handle to the specified file. This is needed to ensure that
|
||||||
|
// harcoded writes to the error handle by e.g. the Go runtime are
|
||||||
|
// redirected to a log file of our choosing.
|
||||||
|
//
|
||||||
|
// We also override os.Stderr for those other parts of Go which use
|
||||||
|
// that and not fd 2 directly.
|
||||||
|
func RedirectStderr(f *os.File) error {
|
||||||
|
osStderrMu.Lock()
|
||||||
|
defer osStderrMu.Unlock()
|
||||||
|
if err := windows.SetStdHandle(windows.STD_ERROR_HANDLE, windows.Handle(f.Fd())); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
os.Stderr = f
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user