fix: allow attaching to any combination of stdin/stdout/stderr
Before this PR, if a stdin/stdout/stderr stream is nil, and the corresponding FIFO is not an empty string, a panic will occur when Read/Write of the nil stream is invoked in io.CopyBuffer. Signed-off-by: Hsing-Yu (David) Chen <davidhsingyuchen@gmail.com>
This commit is contained in:
parent
40f26543bd
commit
687a5f51a8
@ -166,6 +166,15 @@ func NewAttach(opts ...Opt) Attach {
|
|||||||
if fifos == nil {
|
if fifos == nil {
|
||||||
return nil, fmt.Errorf("cannot attach, missing fifos")
|
return nil, fmt.Errorf("cannot attach, missing fifos")
|
||||||
}
|
}
|
||||||
|
if streams.Stdin == nil {
|
||||||
|
fifos.Stdin = ""
|
||||||
|
}
|
||||||
|
if streams.Stdout == nil {
|
||||||
|
fifos.Stdout = ""
|
||||||
|
}
|
||||||
|
if streams.Stderr == nil {
|
||||||
|
fifos.Stderr = ""
|
||||||
|
}
|
||||||
return copyIO(fifos, streams)
|
return copyIO(fifos, streams)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,19 +138,62 @@ func TestNewFIFOSetInDir(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNewAttach(t *testing.T) {
|
func TestNewAttach(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
expectedStdin, expectedStdout, expectedStderr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "attach to all streams (stdin, stdout, and stderr)",
|
||||||
|
expectedStdin: "this is the stdin",
|
||||||
|
expectedStdout: "this is the stdout",
|
||||||
|
expectedStderr: "this is the stderr",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "don't attach to stdin",
|
||||||
|
expectedStdout: "this is the stdout",
|
||||||
|
expectedStderr: "this is the stderr",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "don't attach to stdout",
|
||||||
|
expectedStdin: "this is the stdin",
|
||||||
|
expectedStderr: "this is the stderr",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "don't attach to stderr",
|
||||||
|
expectedStdin: "this is the stdin",
|
||||||
|
expectedStdout: "this is the stdout",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
expectedStdin = "this is the stdin"
|
stdin = bytes.NewBufferString(tc.expectedStdin)
|
||||||
expectedStdout = "this is the stdout"
|
|
||||||
expectedStderr = "this is the stderr"
|
|
||||||
stdin = bytes.NewBufferString(expectedStdin)
|
|
||||||
stdout = new(bytes.Buffer)
|
stdout = new(bytes.Buffer)
|
||||||
stderr = new(bytes.Buffer)
|
stderr = new(bytes.Buffer)
|
||||||
)
|
|
||||||
|
|
||||||
withBytesBuffers := func(streams *Streams) {
|
// The variables below have to be of the interface type (i.e., io.Reader/io.Writer)
|
||||||
*streams = Streams{Stdin: stdin, Stdout: stdout, Stderr: stderr}
|
// instead of the concrete type (i.e., *bytes.Buffer) *before* being passed to NewAttach.
|
||||||
|
// Otherwise, in NewAttach, the interface value won't be nil
|
||||||
|
// (it's just that the concrete value inside the interface itself is nil. [1]),
|
||||||
|
// which means that the corresponding FIFO path won't be set to be an empty string,
|
||||||
|
// and that's not what we want.
|
||||||
|
//
|
||||||
|
// [1] https://go.dev/tour/methods/12
|
||||||
|
stdinArg io.Reader
|
||||||
|
stdoutArg, stderrArg io.Writer
|
||||||
|
)
|
||||||
|
if tc.expectedStdin != "" {
|
||||||
|
stdinArg = stdin
|
||||||
}
|
}
|
||||||
attacher := NewAttach(withBytesBuffers)
|
if tc.expectedStdout != "" {
|
||||||
|
stdoutArg = stdout
|
||||||
|
}
|
||||||
|
if tc.expectedStderr != "" {
|
||||||
|
stderrArg = stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
attacher := NewAttach(WithStreams(stdinArg, stdoutArg, stderrArg))
|
||||||
|
|
||||||
fifos, err := NewFIFOSetInDir("", "theid", false)
|
fifos, err := NewFIFOSetInDir("", "theid", false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -160,18 +203,23 @@ func TestNewAttach(t *testing.T) {
|
|||||||
defer attachedFifos.Close()
|
defer attachedFifos.Close()
|
||||||
|
|
||||||
producers := setupFIFOProducers(t, attachedFifos.Config())
|
producers := setupFIFOProducers(t, attachedFifos.Config())
|
||||||
initProducers(t, producers, expectedStdout, expectedStderr)
|
initProducers(t, producers, tc.expectedStdout, tc.expectedStderr)
|
||||||
|
|
||||||
actualStdin, err := io.ReadAll(producers.Stdin)
|
var actualStdin []byte
|
||||||
|
if producers.Stdin != nil {
|
||||||
|
actualStdin, err = io.ReadAll(producers.Stdin)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
attachedFifos.Wait()
|
attachedFifos.Wait()
|
||||||
attachedFifos.Cancel()
|
attachedFifos.Cancel()
|
||||||
assert.Nil(t, attachedFifos.Close())
|
assert.Nil(t, attachedFifos.Close())
|
||||||
|
|
||||||
assert.Equal(t, expectedStdout, stdout.String())
|
assert.Equal(t, tc.expectedStdout, stdout.String())
|
||||||
assert.Equal(t, expectedStderr, stderr.String())
|
assert.Equal(t, tc.expectedStderr, stderr.String())
|
||||||
assert.Equal(t, expectedStdin, string(actualStdin))
|
assert.Equal(t, tc.expectedStdin, string(actualStdin))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type producers struct {
|
type producers struct {
|
||||||
@ -187,26 +235,36 @@ func setupFIFOProducers(t *testing.T, fifos Config) producers {
|
|||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if fifos.Stdin != "" {
|
||||||
pipes.Stdin, err = fifo.OpenFifo(ctx, fifos.Stdin, syscall.O_RDONLY, 0)
|
pipes.Stdin, err = fifo.OpenFifo(ctx, fifos.Stdin, syscall.O_RDONLY, 0)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fifos.Stdout != "" {
|
||||||
pipes.Stdout, err = fifo.OpenFifo(ctx, fifos.Stdout, syscall.O_WRONLY, 0)
|
pipes.Stdout, err = fifo.OpenFifo(ctx, fifos.Stdout, syscall.O_WRONLY, 0)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fifos.Stderr != "" {
|
||||||
pipes.Stderr, err = fifo.OpenFifo(ctx, fifos.Stderr, syscall.O_WRONLY, 0)
|
pipes.Stderr, err = fifo.OpenFifo(ctx, fifos.Stderr, syscall.O_WRONLY, 0)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
return pipes
|
return pipes
|
||||||
}
|
}
|
||||||
|
|
||||||
func initProducers(t *testing.T, producers producers, stdout, stderr string) {
|
func initProducers(t *testing.T, producers producers, stdout, stderr string) {
|
||||||
|
if producers.Stdout != nil {
|
||||||
_, err := producers.Stdout.Write([]byte(stdout))
|
_, err := producers.Stdout.Write([]byte(stdout))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Nil(t, producers.Stdout.Close())
|
assert.Nil(t, producers.Stdout.Close())
|
||||||
|
}
|
||||||
|
|
||||||
_, err = producers.Stderr.Write([]byte(stderr))
|
if producers.Stderr != nil {
|
||||||
|
_, err := producers.Stderr.Write([]byte(stderr))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Nil(t, producers.Stderr.Close())
|
assert.Nil(t, producers.Stderr.Close())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLogURIGenerator(t *testing.T) {
|
func TestLogURIGenerator(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user