mirror of
https://github.com/docker/cli.git
synced 2026-11-06 13:10:41 -04:00
TerminatePrompt created a pipe for the prompt's stdin but discarded the write end. Once the garbage collector finalized it, the write end was closed, the prompt read EOF and treated it as "no", and the command returned "prune has been cancelled" instead of prompt.ErrTerminated. This keeps both ends of the pipe referenced and closes them in t.Cleanup. This change also drops the OutBuffer().Len() check: the writer hook only fires after the write has completed, so the check proved nothing, and it raced with the prompt's later writes under -race. This change also applies the same fix to the identical copy in cmd/docker-trust. Signed-off-by: Hamir <hirehamir@gmail.com>
85 lines
1.9 KiB
Go
85 lines
1.9 KiB
Go
package test
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/docker/cli/cli/streams"
|
|
"github.com/docker/cli/internal/prompt"
|
|
"github.com/spf13/cobra"
|
|
"gotest.tools/v3/assert"
|
|
)
|
|
|
|
func TerminatePrompt(ctx context.Context, t *testing.T, cmd *cobra.Command, cli *FakeCli) {
|
|
t.Helper()
|
|
|
|
errChan := make(chan error)
|
|
defer close(errChan)
|
|
|
|
// wrap the out stream to detect when the prompt is ready
|
|
writerHookChan := make(chan struct{})
|
|
defer close(writerHookChan)
|
|
|
|
outStream := streams.NewOut(NewWriterWithHook(cli.OutBuffer(), func(p []byte) {
|
|
writerHookChan <- struct{}{}
|
|
}))
|
|
cli.SetOut(outStream)
|
|
|
|
r, w, err := os.Pipe()
|
|
assert.NilError(t, err)
|
|
t.Cleanup(func() {
|
|
_ = w.Close()
|
|
_ = r.Close()
|
|
})
|
|
|
|
cli.SetIn(streams.NewIn(r))
|
|
|
|
notifyCtx, notifyCancel := context.WithCancel(ctx)
|
|
t.Cleanup(notifyCancel)
|
|
|
|
go func() {
|
|
errChan <- cmd.ExecuteContext(notifyCtx)
|
|
}()
|
|
|
|
writeCtx, writeCancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
|
defer writeCancel()
|
|
|
|
// wait for the prompt to be ready
|
|
select {
|
|
case <-writeCtx.Done():
|
|
t.Fatalf("command %s did not write prompt to stdout", cmd.Name())
|
|
case <-writerHookChan:
|
|
// drain the channel for future buffer writes
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-writerHookChan:
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// a small delay to ensure the plugin is prompting
|
|
time.Sleep(100 * time.Microsecond)
|
|
|
|
errCtx, errCancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
|
defer errCancel()
|
|
|
|
// sigint and sigterm are caught by the prompt
|
|
// this allows us to gracefully exit the prompt with a 0 exit code
|
|
notifyCancel()
|
|
|
|
select {
|
|
case <-errCtx.Done():
|
|
t.Logf("command stdout:\n%s\n", cli.OutBuffer().String())
|
|
t.Logf("command stderr:\n%s\n", cli.ErrBuffer().String())
|
|
t.Fatalf("command %s did not return after SIGINT", cmd.Name())
|
|
case err := <-errChan:
|
|
assert.ErrorIs(t, err, prompt.ErrTerminated)
|
|
}
|
|
}
|