ghttp handles failures and panics in handlers
- If a registered handler makes a failing assertion ghttp will return 500.
- If a registered handler panics, ghttp will return 500 *and* fail the test. This is new behavior that may cause existing code to break. This code is almost certainly incorrect and creating a false positive.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f2c1ed..0eec5a8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,9 @@
- Added `HavePrefix` and `HaveSuffix` matchers.
- `ghttp` can now handle concurrent requests.
- Added `Succeed` which allows one to write `Ω(MyFunction()).Should(Succeed())`.
+- Improved `ghttp`'s behavior around failing assertions and panics:
+ - If a registered handler makes a failing assertion `ghttp` will return `500`.
+ - If a registered handler panics, `ghttp` will return `500` *and* fail the test. This is new behavior that may cause existing code to break. This code is almost certainly incorrect and creating a false positive.
Bug Fixes:
- gexec: `session.Wait` now uses `EventuallyWithOffset` to get the right line number in the failure.
diff --git a/ghttp/test_server.go b/ghttp/test_server.go
index 4bbb2e4..7b4a996 100644
--- a/ghttp/test_server.go
+++ b/ghttp/test_server.go
@@ -111,6 +111,7 @@
"net/http/httptest"
"reflect"
"regexp"
+ "strings"
"sync"
. "github.com/onsi/gomega"
@@ -208,7 +209,29 @@
func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
s.writeLock.Lock()
defer func() {
- recover()
+ e := recover()
+ if e != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ }
+
+ //If the handler panics GHTTP will silently succeed. This is bad™.
+ //To catch this case we need to fail the test if the handler has panicked.
+ //However, if the handler is panicking because Ginkgo's causing it to panic (i.e. an asswertion failed)
+ //then we shouldn't double-report the error as this will confuse people.
+
+ //So: step 1, if this is a Ginkgo panic - do nothing, Ginkgo's aware of the failure
+ eAsString, ok := e.(string)
+ if ok && strings.Contains(eAsString, "defer GinkgoRecover()") {
+ return
+ }
+
+ //If we're here, we have to do step 2: assert that the error is nil. This assertion will
+ //allow us to fail the test suite (note: we can't call Fail since Gomega is not allowed to import Ginkgo).
+ //Since a failed assertion throws a panic, and we are likely in a goroutine, we need to defer within our defer!
+ defer func() {
+ recover()
+ }()
+ Ω(e).Should(BeNil(), "Handler Panicked")
}()
s.receivedRequests = append(s.receivedRequests, req)
diff --git a/ghttp/test_server_test.go b/ghttp/test_server_test.go
index 82aa3aa..25260b6 100644
--- a/ghttp/test_server_test.go
+++ b/ghttp/test_server_test.go
@@ -192,6 +192,51 @@
})
})
+ Describe("When a handler fails", func() {
+ BeforeEach(func() {
+ s.UnhandledRequestStatusCode = http.StatusForbidden //just to be clear that 500s aren't coming from unhandled requests
+ })
+
+ Context("because the handler has panicked", func() {
+ BeforeEach(func() {
+ s.AppendHandlers(func(w http.ResponseWriter, req *http.Request) {
+ panic("bam")
+ })
+ })
+
+ It("should respond with a 500 and make a failing assertion", func() {
+ var resp *http.Response
+ var err error
+
+ failures := InterceptGomegaFailures(func() {
+ resp, err = http.Get(s.URL())
+ })
+
+ Ω(err).ShouldNot(HaveOccurred())
+ Ω(resp.StatusCode).Should(Equal(http.StatusInternalServerError))
+ Ω(failures).Should(ConsistOf(ContainSubstring("Handler Panicked")))
+ })
+ })
+
+ Context("because an assertion has failed", func() {
+ BeforeEach(func() {
+ s.AppendHandlers(func(w http.ResponseWriter, req *http.Request) {
+ // Ω(true).Should(BeFalse()) <-- would be nice to do it this way, but the test just can't be written this way
+
+ By("We're cheating a bit here -- we're throwing a GINKGO_PANIC which simulates a failed assertion")
+ panic(GINKGO_PANIC)
+ })
+ })
+
+ It("should respond with a 500 and *not* make a failing assertion, instead relying on Ginkgo to have already been notified of the error", func() {
+ resp, err := http.Get(s.URL())
+
+ Ω(err).ShouldNot(HaveOccurred())
+ Ω(resp.StatusCode).Should(Equal(http.StatusInternalServerError))
+ })
+ })
+ })
+
Describe("Request Handlers", func() {
Describe("VerifyRequest", func() {
BeforeEach(func() {
@@ -310,7 +355,7 @@
failures := InterceptGomegaFailures(func() {
http.DefaultClient.Do(req)
})
- Ω(failures).Should(HaveLen(1))
+ Ω(failures).Should(ContainElement(ContainSubstring("Authorization header must be specified")))
})
})