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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user