This commit adds a new testutil.CaptureStdio helper so that we
can test external go modules that use os.Std{out,err} but now
allow mocking or overwriting.
112 lines
2.1 KiB
Go
112 lines
2.1 KiB
Go
package testutil
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type MockCmd struct {
|
|
binDir string
|
|
name string
|
|
}
|
|
|
|
func MockCommand(t *testing.T, name string, script string) *MockCmd {
|
|
mockCmd := &MockCmd{
|
|
binDir: t.TempDir(),
|
|
name: name,
|
|
}
|
|
|
|
fullScript := `#!/bin/bash -e
|
|
for arg in "$@"; do
|
|
echo -e -n "$arg\x0" >> "$0".run
|
|
done
|
|
echo >> "$0".run
|
|
` + script
|
|
|
|
t.Setenv("PATH", mockCmd.binDir+":"+os.Getenv("PATH"))
|
|
err := os.WriteFile(filepath.Join(mockCmd.binDir, name), []byte(fullScript), 0755)
|
|
require.NoError(t, err)
|
|
|
|
return mockCmd
|
|
}
|
|
|
|
func (mc *MockCmd) Path() string {
|
|
return filepath.Join(mc.binDir, mc.name)
|
|
}
|
|
|
|
func (mc *MockCmd) Restore() error {
|
|
return os.RemoveAll(mc.binDir)
|
|
}
|
|
|
|
func (mc *MockCmd) Calls() [][]string {
|
|
b, err := os.ReadFile(mc.Path() + ".run")
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
var calls [][]string
|
|
for _, line := range strings.Split(string(b), "\n") {
|
|
if line == "" {
|
|
continue
|
|
}
|
|
call := strings.Split(line, "\000")
|
|
calls = append(calls, call[0:len(call)-1])
|
|
}
|
|
return calls
|
|
}
|
|
|
|
// CaptureStdio runs the given function f() in an environment that
|
|
// captures stdout, stderr and returns the the result as string.
|
|
//
|
|
// Use this very targeted to avoid real stdout/stderr output
|
|
// from being displayed.
|
|
func CaptureStdio(t *testing.T, f func()) (string, string) {
|
|
saved1 := os.Stdout
|
|
saved2 := os.Stderr
|
|
|
|
r1, w1, err := os.Pipe()
|
|
require.NoError(t, err)
|
|
defer r1.Close()
|
|
defer w1.Close()
|
|
r2, w2, err := os.Pipe()
|
|
require.NoError(t, err)
|
|
defer r2.Close()
|
|
defer w2.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
var stdout, stderr bytes.Buffer
|
|
wg.Add(1)
|
|
// this needs to be a go-routines or we could deadlock
|
|
// when the pipe is full
|
|
go func() {
|
|
defer wg.Done()
|
|
io.Copy(&stdout, r1)
|
|
}()
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
io.Copy(&stderr, r2)
|
|
}()
|
|
|
|
os.Stdout = w1
|
|
os.Stderr = w2
|
|
defer func() {
|
|
os.Stdout = saved1
|
|
os.Stderr = saved2
|
|
}()
|
|
|
|
f()
|
|
w1.Close()
|
|
w2.Close()
|
|
wg.Wait()
|
|
return stdout.String(), stderr.String()
|
|
}
|