From 9a35314fdb24a83d82bca3a7e3056c508b1b005c Mon Sep 17 00:00:00 2001 From: Ryan Moran Date: Mon, 22 Jun 2020 12:21:39 -0400 Subject: [PATCH] Adds support for a WithMessage modifier on packit.Fail For example, a call to `packit.Fail.WithMessage("my error")` will result in an error that triggers the correct fail exit code for the buildpack lifecycle, and also included the given message. This change is backwards compatible and so should not impact any buildpacks currently just using `packit.Fail`. --- detect.go | 5 ----- detect_test.go | 19 +++++++++++++++---- fail.go | 12 ++++++++++++ internal/exit_handler.go | 7 ++----- internal/fail.go | 16 ++++++++++++++++ internal/fail_test.go | 28 ++++++++++++++++++++++++++++ internal/init_test.go | 1 + 7 files changed, 74 insertions(+), 14 deletions(-) create mode 100644 fail.go create mode 100644 internal/fail.go create mode 100644 internal/fail_test.go diff --git a/detect.go b/detect.go index 232a0255..50ccbecd 100644 --- a/detect.go +++ b/detect.go @@ -9,11 +9,6 @@ import ( "github.com/paketo-buildpacks/packit/internal" ) -// Fail is a sentinal value that can be used to indicate a failure to detect -// during the detect phase. Fail implements the Error interface and should be -// returned as the error value in the DetectFunc signature. -var Fail = internal.Fail - // DetectContext provides the contextual details that are made available by the // buildpack lifecycle during the detect phase. This context is populated by // the Detect function and passed to the DetectFunc during execution. diff --git a/detect_test.go b/detect_test.go index b7cfe227..d5b2d1ac 100644 --- a/detect_test.go +++ b/detect_test.go @@ -1,6 +1,7 @@ package packit_test import ( + "bytes" "errors" "io/ioutil" "os" @@ -230,14 +231,24 @@ some-key = "some-value" context("when the DetectFunc fails", func() { it("calls the ExitHandler with the correct exit code", func() { var exitCode int + buffer := bytes.NewBuffer(nil) packit.Detect(func(ctx packit.DetectContext) (packit.DetectResult, error) { - return packit.DetectResult{}, packit.Fail - }, packit.WithArgs([]string{binaryPath, "", ""}), packit.WithExitHandler(internal.NewExitHandler(internal.WithExitHandlerExitFunc(func(code int) { - exitCode = code - })))) + return packit.DetectResult{}, packit.Fail.WithMessage("failure message") + }, + packit.WithArgs([]string{binaryPath, "", ""}), + packit.WithExitHandler( + internal.NewExitHandler( + internal.WithExitHandlerExitFunc(func(code int) { + exitCode = code + }), + internal.WithExitHandlerStderr(buffer), + ), + ), + ) Expect(exitCode).To(Equal(100)) + Expect(buffer.String()).To(Equal("failure message\n")) }) }) diff --git a/fail.go b/fail.go new file mode 100644 index 00000000..ae69c0bd --- /dev/null +++ b/fail.go @@ -0,0 +1,12 @@ +package packit + +import "github.com/paketo-buildpacks/packit/internal" + +// Fail is a sentinal value that can be used to indicate a failure to detect +// during the detect phase. Fail implements the Error interface and should be +// returned as the error value in the DetectFunc signature. Fail also supports +// a modifier function, WithMessage, that allows the caller to set a custom +// failure message. The WithMessage function supports a fmt.Printf-like format +// string and variadic arguments to build a message, eg: +// packit.Fail.WithMessage("failed: %w", err). +var Fail = internal.Fail diff --git a/internal/exit_handler.go b/internal/exit_handler.go index 9e4f480e..8bb1306a 100644 --- a/internal/exit_handler.go +++ b/internal/exit_handler.go @@ -1,14 +1,11 @@ package internal import ( - "errors" "fmt" "io" "os" ) -var Fail = errors.New("failed") - type Option func(handler ExitHandler) ExitHandler func WithExitHandlerStderr(stderr io.Writer) Option { @@ -57,8 +54,8 @@ func (h ExitHandler) Error(err error) { fmt.Fprintln(h.stderr, err) var code int - switch err { - case Fail: + switch err.(type) { + case failError: code = 100 case nil: code = 0 diff --git a/internal/fail.go b/internal/fail.go new file mode 100644 index 00000000..b1393759 --- /dev/null +++ b/internal/fail.go @@ -0,0 +1,16 @@ +package internal + +import ( + "errors" + "fmt" +) + +var Fail = failError{error: errors.New("failed")} + +type failError struct { + error +} + +func (f failError) WithMessage(format string, v ...interface{}) failError { + return failError{error: fmt.Errorf(format, v...)} +} diff --git a/internal/fail_test.go b/internal/fail_test.go new file mode 100644 index 00000000..055aeb65 --- /dev/null +++ b/internal/fail_test.go @@ -0,0 +1,28 @@ +package internal_test + +import ( + "testing" + + "github.com/paketo-buildpacks/packit/internal" + "github.com/sclevine/spec" + + . "github.com/onsi/gomega" +) + +func testFail(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + ) + + it("acts as an error", func() { + fail := internal.Fail + Expect(fail).To(MatchError("failed")) + }) + + context("when given a message", func() { + it("acts as an error with that message", func() { + fail := internal.Fail.WithMessage("this is a %s", "failure message") + Expect(fail).To(MatchError("this is a failure message")) + }) + }) +} diff --git a/internal/init_test.go b/internal/init_test.go index e30c9e54..baf05c62 100644 --- a/internal/init_test.go +++ b/internal/init_test.go @@ -11,6 +11,7 @@ func TestUnitInternal(t *testing.T) { suite := spec.New("packit/internal", spec.Report(report.Terminal{})) suite("EnvironmentWriter", testEnvironmentWriter) suite("ExitHandler", testExitHandler) + suite("Fail", testFail) suite("TOMLWriter", testTOMLWriter) suite.Run(t) }