package stream import ( "context" "io" "testing" "time" "gotest.tools/v3/assert" ) // Make sure when there is no I/O on a stream that the goroutines do not get blocked after the container exits. func TestAttachNoIO(t *testing.T) { t.Run("stdin only", func(t *testing.T) { stdinR, _ := io.Pipe() defer stdinR.Close() testStreamCopy(t, stdinR, nil, nil) }) t.Run("stdout only", func(t *testing.T) { _, w := io.Pipe() defer w.Close() testStreamCopy(t, nil, w, nil) }) t.Run("stderr only", func(t *testing.T) { _, w := io.Pipe() defer w.Close() testStreamCopy(t, nil, nil, w) }) t.Run("stdout+stderr", func(t *testing.T) { _, stdoutW := io.Pipe() defer stdoutW.Close() _, stderrW := io.Pipe() defer stderrW.Close() testStreamCopy(t, nil, stdoutW, stderrW) }) t.Run("stdin+stdout", func(t *testing.T) { stdin, _ := io.Pipe() defer stdin.Close() _, stdout := io.Pipe() defer stdout.Close() testStreamCopy(t, stdin, stdout, nil) }) t.Run("stdin+stderr", func(t *testing.T) { stdin, _ := io.Pipe() defer stdin.Close() _, stderr := io.Pipe() defer stderr.Close() testStreamCopy(t, stdin, nil, stderr) }) t.Run("stdin+stdout+stderr", func(t *testing.T) { stdinR, _ := io.Pipe() defer stdinR.Close() stdoutR, stdoutW := io.Pipe() defer stdoutR.Close() stderrR, stderrW := io.Pipe() defer stderrR.Close() testStreamCopy(t, stdinR, stdoutW, stderrW) }) } func testStreamCopy(t *testing.T, stdin io.ReadCloser, stdout, stderr io.WriteCloser) { cfg := AttachConfig{ UseStdin: stdin != nil, UseStdout: stdout != nil, UseStderr: stderr != nil, Stdin: stdin, Stdout: stdout, Stderr: stderr, } sc := NewConfig() sc.AttachStreams(&cfg) defer sc.CloseStreams() ctx, cancel := context.WithCancel(context.Background()) defer cancel() chErr := sc.CopyStreams(ctx, &cfg) select { case err := <-chErr: assert.NilError(t, err) default: } cancel() select { case err := <-chErr: assert.ErrorIs(t, err, context.Canceled) case <-time.After(10 * time.Second): t.Fatal("timeout waiting for CopyStreams to exit") } }