diff --git a/plugin/plugin.go b/plugin/plugin.go index 8b8959a3..cc10cf5e 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -222,6 +222,9 @@ func (c execCommander) Output(ctx context.Context, name string, command plugin.C cmd.Stdout = &stdout err := cmd.Run() if err != nil { + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return nil, stderr.Bytes(), fmt.Errorf("'%s %s' command execution timeout: %s", name, string(command), err.Error()); + } return nil, stderr.Bytes(), err } return stdout.Bytes(), nil, nil diff --git a/plugin/plugin_test.go b/plugin/plugin_test.go index 171767b8..84070e1c 100644 --- a/plugin/plugin_test.go +++ b/plugin/plugin_test.go @@ -19,9 +19,11 @@ import ( "errors" "os" "reflect" + "runtime" "strconv" "strings" "testing" + "time" "github.com/notaryproject/notation-go/plugin/proto" ) @@ -181,7 +183,7 @@ func TestValidateMetadata(t *testing.T) { } } -func TestNewCLIPlugin_PathError(t *testing.T) { +func TestNewCLIPlugin_Error(t *testing.T) { ctx := context.Background() t.Run("plugin directory exists without executable.", func(t *testing.T) { p, err := NewCLIPlugin(ctx, "emptyplugin", "./testdata/plugins/emptyplugin/notation-emptyplugin") @@ -203,6 +205,26 @@ func TestNewCLIPlugin_PathError(t *testing.T) { t.Errorf("NewCLIPlugin() plugin = %v, want nil", p) } }) + + t.Run("plugin timeout error", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on Windows") + } + expectedErrMsg := "'sleep 10' command execution timeout: signal: killed" + ctxWithTimout, cancel := context.WithTimeout(ctx, 10 * time.Millisecond) + defer cancel() + + var twoSeconds proto.Command + twoSeconds = "10" + + _, _, err := execCommander{}.Output(ctxWithTimout, "sleep", twoSeconds, nil); + if err == nil { + t.Errorf("execCommander{}.Output() expected error = %v, got nil", expectedErrMsg) + } + if err.Error() != expectedErrMsg { + t.Errorf("execCommander{}.Output() error = %v, want %v", err, expectedErrMsg) + } + }) } func TestNewCLIPlugin_ValidError(t *testing.T) {