From d7036970e853489de0f81c0b26429833d178eef6 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 3 Sep 2026 21:49:09 +0200 Subject: [PATCH] cmd/docker-trust: fix tests Some test-files were missing. Signed-off-by: Sebastiaan van Stijn --- cmd/docker-trust/internal/test/cmd.go | 81 ++++++++++++++++++++++++ cmd/docker-trust/internal/test/writer.go | 25 ++++++++ cmd/docker-trust/trust/inspect.go | 2 +- 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 cmd/docker-trust/internal/test/cmd.go create mode 100644 cmd/docker-trust/internal/test/writer.go diff --git a/cmd/docker-trust/internal/test/cmd.go b/cmd/docker-trust/internal/test/cmd.go new file mode 100644 index 0000000000..8b98d2df62 --- /dev/null +++ b/cmd/docker-trust/internal/test/cmd.go @@ -0,0 +1,81 @@ +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, _, err := os.Pipe() + assert.NilError(t, err) + 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: + } + } + }() + } + + assert.Check(t, cli.OutBuffer().Len() > 0) + + // 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) + } +} diff --git a/cmd/docker-trust/internal/test/writer.go b/cmd/docker-trust/internal/test/writer.go new file mode 100644 index 0000000000..3d922fb57a --- /dev/null +++ b/cmd/docker-trust/internal/test/writer.go @@ -0,0 +1,25 @@ +package test + +import ( + "io" +) + +type writerWithHook struct { + actualWriter io.Writer + hook func([]byte) +} + +func (w *writerWithHook) Write(p []byte) (n int, err error) { + defer w.hook(p) + return w.actualWriter.Write(p) +} + +var _ io.Writer = (*writerWithHook)(nil) + +// NewWriterWithHook returns a io.Writer that still +// writes to the actualWriter but also calls the hook function +// after every write. It is useful to use this function when +// you need to wait for a writer to complete writing inside a test. +func NewWriterWithHook(actualWriter io.Writer, hook func([]byte)) *writerWithHook { + return &writerWithHook{actualWriter: actualWriter, hook: hook} +} diff --git a/cmd/docker-trust/trust/inspect.go b/cmd/docker-trust/trust/inspect.go index 74240b61ad..fe16121ce7 100644 --- a/cmd/docker-trust/trust/inspect.go +++ b/cmd/docker-trust/trust/inspect.go @@ -81,7 +81,7 @@ func getRepoTrustInfo(ctx context.Context, dockerCLI command.Cli, remote string) } } - var signerList []trustSigner + signerList := []trustSigner{} for signerName, signerKeys := range getDelegationRoleToKeyMap(delegationRoles) { signerKeyList := make([]trustKey, 0, len(signerKeys)) for _, keyID := range signerKeys {