Merge pull request #176 from petergtz/master

Print panic object when using ShouldNot(Panic())
diff --git a/format/format.go b/format/format.go
index 06355d9..5cd89bc 100644
--- a/format/format.go
+++ b/format/format.go
@@ -6,8 +6,9 @@
 import (
 	"fmt"
 	"reflect"
-	"strings"
 	"strconv"
+	"strings"
+	"time"
 )
 
 // Use MaxDepth to set the maximum recursion depth when printing deeply nested objects
@@ -22,6 +23,24 @@
 */
 var UseStringerRepresentation = false
 
+/*
+Print the content of context objects. By default it will be suppressed.
+
+Set PrintContextObjects = true to enable printing of the context internals.
+*/
+var PrintContextObjects = false
+
+// Ctx interface defined here to keep backwards compatability with go < 1.7
+// It matches the context.Context interface
+type Ctx interface {
+	Deadline() (deadline time.Time, ok bool)
+	Done() <-chan struct{}
+	Err() error
+	Value(key interface{}) interface{}
+}
+
+var contextType = reflect.TypeOf((*Ctx)(nil)).Elem()
+
 //The default indentation string emitted by the format package
 var Indent = "    "
 
@@ -57,6 +76,8 @@
 Modify format.MaxDepth to control how deep the recursion is allowed to go
 Set format.UseStringerRepresentation to true to return object.GoString() or object.String() when available instead of
 recursing into the object.
+
+Set PrintContextObjects to true to print the content of objects implementing context.Context
 */
 func Object(object interface{}, indentation uint) string {
 	indent := strings.Repeat(Indent, int(indentation))
@@ -124,6 +145,12 @@
 		}
 	}
 
+	if !PrintContextObjects {
+		if value.Type().Implements(contextType) && indentation > 1 {
+			return "<suppressed context>"
+		}
+	}
+
 	switch value.Kind() {
 	case reflect.Bool:
 		return fmt.Sprintf("%v", value.Bool())
@@ -187,7 +214,7 @@
 }
 
 func formatSlice(v reflect.Value, indentation uint) string {
-	if v.Kind() == reflect.Slice && v.Type().Elem().Kind() == reflect.Uint8 && isPrintableString(string(v.Bytes())){
+	if v.Kind() == reflect.Slice && v.Type().Elem().Kind() == reflect.Uint8 && isPrintableString(string(v.Bytes())) {
 		return formatString(v.Bytes(), indentation)
 	}
 
@@ -216,7 +243,7 @@
 	longest := 0
 	for i, key := range v.MapKeys() {
 		value := v.MapIndex(key)
-		result[i] = fmt.Sprintf("%s: %s", formatValue(key, 0), formatValue(value, indentation+1))
+		result[i] = fmt.Sprintf("%s: %s", formatValue(key, indentation+1), formatValue(value, indentation+1))
 		if len(result[i]) > longest {
 			longest = len(result[i])
 		}
diff --git a/format/format_test.go b/format/format_test.go
index 1391fe7..4d4b36c 100644
--- a/format/format_test.go
+++ b/format/format_test.go
@@ -2,11 +2,13 @@
 
 import (
 	"fmt"
+	"strings"
+	"time"
+
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	. "github.com/onsi/gomega/format"
 	"github.com/onsi/gomega/types"
-	"strings"
 )
 
 //recursive struct
@@ -71,6 +73,25 @@
 	return "string"
 }
 
+type ctx struct {
+}
+
+func (c *ctx) Deadline() (deadline time.Time, ok bool) {
+	return time.Time{}, false
+}
+
+func (c *ctx) Done() <-chan struct{} {
+	return nil
+}
+
+func (c *ctx) Err() error {
+	return nil
+}
+
+func (c *ctx) Value(key interface{}) interface{} {
+	return nil
+}
+
 var _ = Describe("Format", func() {
 	match := func(typeRepresentation string, valueRepresentation string, args ...interface{}) types.GomegaMatcher {
 		if len(args) > 0 {
@@ -162,19 +183,19 @@
 		})
 
 		Describe("formatting []byte slices", func() {
-      Context("when the slice is made of printable bytes", func () {
-        It("should present it as string", func() {
-          b := []byte("a b c")
-          Ω(Object(b, 1)).Should(matchRegexp(`\[\]uint8 \| len:5, cap:\d+`, `a b c`))
-        })
-      })
-      Context("when the slice contains non-printable bytes", func () {
-        It("should present it as slice", func() {
-          b := []byte("a b c\n\x01\x02\x03\xff\x1bH")
-          Ω(Object(b, 1)).Should(matchRegexp(`\[\]uint8 \| len:12, cap:\d+`,  `\[97, 32, 98, 32, 99, 10, 1, 2, 3, 255, 27, 72\]`))
-        })
-      })
-    })
+			Context("when the slice is made of printable bytes", func() {
+				It("should present it as string", func() {
+					b := []byte("a b c")
+					Ω(Object(b, 1)).Should(matchRegexp(`\[\]uint8 \| len:5, cap:\d+`, `a b c`))
+				})
+			})
+			Context("when the slice contains non-printable bytes", func() {
+				It("should present it as slice", func() {
+					b := []byte("a b c\n\x01\x02\x03\xff\x1bH")
+					Ω(Object(b, 1)).Should(matchRegexp(`\[\]uint8 \| len:12, cap:\d+`, `\[97, 32, 98, 32, 99, 10, 1, 2, 3, 255, 27, 72\]`))
+				})
+			})
+		})
 
 		Describe("formatting functions", func() {
 			It("should give the type and format values correctly", func() {
@@ -429,6 +450,18 @@
 			m["map"] = m
 			Ω(Object(m, 1)).Should(ContainSubstring("..."))
 		})
+
+		It("really should not go crazy...", func() {
+			type complexKey struct {
+				Value map[interface{}]int
+			}
+
+			complexObject := complexKey{}
+			complexObject.Value = make(map[interface{}]int)
+
+			complexObject.Value[&complexObject] = 2
+			Ω(Object(complexObject, 1)).Should(ContainSubstring("..."))
+		})
 	})
 
 	Describe("When instructed to use the Stringer representation", func() {
@@ -452,4 +485,37 @@
 			})
 		})
 	})
+
+	Describe("Printing a context.Context field", func() {
+
+		type structWithContext struct {
+			Context Ctx
+			Value   string
+		}
+
+		context := ctx{}
+		objWithContext := structWithContext{Value: "some-value", Context: &context}
+
+		It("Suppresses the content by default", func() {
+			Ω(Object(objWithContext, 1)).Should(ContainSubstring("<suppressed context>"))
+		})
+
+		It("Doesn't supress the context if it's the object being printed", func() {
+			Ω(Object(context, 1)).ShouldNot(MatchRegexp("^.*<suppressed context>$"))
+		})
+
+		Context("PrintContextObjects is set", func() {
+			BeforeEach(func() {
+				PrintContextObjects = true
+			})
+
+			AfterEach(func() {
+				PrintContextObjects = false
+			})
+
+			It("Prints the context", func() {
+				Ω(Object(objWithContext, 1)).ShouldNot(ContainSubstring("<suppressed context>"))
+			})
+		})
+	})
 })
diff --git a/gexec/session.go b/gexec/session.go
index c6cc127..387a72c 100644
--- a/gexec/session.go
+++ b/gexec/session.go
@@ -92,6 +92,9 @@
 	err := command.Start()
 	if err == nil {
 		go session.monitorForExit(exited)
+		trackedSessionsMutex.Lock()
+		defer trackedSessionsMutex.Unlock()
+		trackedSessions = append(trackedSessions, session)
 	}
 
 	return session, err
@@ -179,7 +182,7 @@
 }
 
 /*
-Terminate sends the running command the passed in signal.  It does not wait for the process to exit.
+Signal sends the running command the passed in signal.  It does not wait for the process to exit.
 
 If the command has already exited, Signal returns silently.
 
@@ -212,3 +215,91 @@
 
 	close(exited)
 }
+
+var trackedSessions = []*Session{}
+var trackedSessionsMutex = &sync.Mutex{}
+
+/*
+Kill sends a SIGKILL signal to all the processes started by Run, and waits for them to exit.
+The timeout specified is applied to each process killed.
+
+If any of the processes already exited, KillAndWait returns silently.
+*/
+func KillAndWait(timeout ...interface{}) {
+	trackedSessionsMutex.Lock()
+	defer trackedSessionsMutex.Unlock()
+	for _, session := range trackedSessions {
+		session.Kill().Wait(timeout...)
+	}
+	trackedSessions = []*Session{}
+}
+
+/*
+Kill sends a SIGTERM signal to all the processes started by Run, and waits for them to exit.
+The timeout specified is applied to each process killed.
+
+If any of the processes already exited, TerminateAndWait returns silently.
+*/
+func TerminateAndWait(timeout ...interface{}) {
+	trackedSessionsMutex.Lock()
+	defer trackedSessionsMutex.Unlock()
+	for _, session := range trackedSessions {
+		session.Terminate().Wait(timeout...)
+	}
+}
+
+/*
+Kill sends a SIGKILL signal to all the processes started by Run.
+It does not wait for the processes to exit.
+
+If any of the processes already exited, Kill returns silently.
+*/
+func Kill() {
+	trackedSessionsMutex.Lock()
+	defer trackedSessionsMutex.Unlock()
+	for _, session := range trackedSessions {
+		session.Kill()
+	}
+}
+
+/*
+Terminate sends a SIGTERM signal to all the processes started by Run.
+It does not wait for the processes to exit.
+
+If any of the processes already exited, Terminate returns silently.
+*/
+func Terminate() {
+	trackedSessionsMutex.Lock()
+	defer trackedSessionsMutex.Unlock()
+	for _, session := range trackedSessions {
+		session.Terminate()
+	}
+}
+
+/*
+Signal sends the passed in signal to all the processes started by Run.
+It does not wait for the processes to exit.
+
+If any of the processes already exited, Signal returns silently.
+*/
+func Signal(signal os.Signal) {
+	trackedSessionsMutex.Lock()
+	defer trackedSessionsMutex.Unlock()
+	for _, session := range trackedSessions {
+		session.Signal(signal)
+	}
+}
+
+/*
+Interrupt sends the SIGINT signal to all the processes started by Run.
+It does not wait for the processes to exit.
+
+If any of the processes already exited, Interrupt returns silently.
+*/
+func Interrupt() {
+	trackedSessionsMutex.Lock()
+	defer trackedSessionsMutex.Unlock()
+	for _, session := range trackedSessions {
+		session.Interrupt()
+	}
+}
diff --git a/gexec/session_test.go b/gexec/session_test.go
index 13cc3cd..b7841a0 100644
--- a/gexec/session_test.go
+++ b/gexec/session_test.go
@@ -84,7 +84,7 @@
 	})
 
 	Describe("kill", func() {
-		It("should kill the command and wait for it to exit", func() {
+		It("should kill the command and don't wait for it to exit", func() {
 			session, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
 			Ω(err).ShouldNot(HaveOccurred())
 
@@ -127,6 +127,179 @@
 		})
 	})
 
+	Context("tracking sessions", func() {
+		BeforeEach(func() {
+			KillAndWait()
+		})
+
+		Describe("kill", func() {
+			It("should kill all the started sessions", func() {
+				session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				Kill()
+
+				Eventually(session1).Should(Exit(128 + 9))
+				Eventually(session2).Should(Exit(128 + 9))
+				Eventually(session3).Should(Exit(128 + 9))
+			})
+
+			It("should not wait for exit", func() {
+				session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				Kill()
+				Ω(session1).ShouldNot(Exit(), "Should not exit immediately...")
+
+				Eventually(session1).Should(Exit(128 + 9))
+			})
+
+			It("should not track unstarted sessions", func() {
+				_, err := Start(exec.Command("does not exist", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).Should(HaveOccurred())
+
+				session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				Kill()
+
+				Eventually(session2).Should(Exit(128 + 9))
+				Eventually(session3).Should(Exit(128 + 9))
+			})
+
+		})
+
+		Describe("killAndWait", func() {
+			It("should kill all the started sessions and wait for them to finish", func() {
+				session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				KillAndWait()
+				Ω(session1).Should(Exit(128+9), "Should have exited")
+				Ω(session2).Should(Exit(128+9), "Should have exited")
+				Ω(session3).Should(Exit(128+9), "Should have exited")
+			})
+		})
+
+		Describe("terminate", func() {
+			It("should terminate all the started sessions", func() {
+				session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				Terminate()
+
+				Eventually(session1).Should(Exit(128 + 15))
+				Eventually(session2).Should(Exit(128 + 15))
+				Eventually(session3).Should(Exit(128 + 15))
+			})
+
+			It("should not wait for exit", func() {
+				session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				Terminate()
+
+				Ω(session1).ShouldNot(Exit(), "Should not exit immediately...")
+			})
+		})
+
+		Describe("terminateAndWait", func() {
+			It("should terminate all the started sessions, and wait for them to exit", func() {
+				session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				TerminateAndWait()
+
+				Ω(session1).Should(Exit(128+15), "Should have exited")
+				Ω(session2).Should(Exit(128+15), "Should have exited")
+				Ω(session3).Should(Exit(128+15), "Should have exited")
+			})
+		})
+
+		Describe("signal", func() {
+			It("should signal all the started sessions", func() {
+				session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				Signal(syscall.SIGABRT)
+
+				Eventually(session1).Should(Exit(128 + 6))
+				Eventually(session2).Should(Exit(128 + 6))
+				Eventually(session3).Should(Exit(128 + 6))
+			})
+
+			It("should not wait", func() {
+				session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				Signal(syscall.SIGABRT)
+
+				Ω(session1).ShouldNot(Exit(), "Should not exit immediately...")
+			})
+		})
+
+		Describe("interrupt", func() {
+			It("should interrupt all the started sessions, and not wait", func() {
+				session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				Interrupt()
+
+				Eventually(session1).Should(Exit(128 + 2))
+				Eventually(session2).Should(Exit(128 + 2))
+				Eventually(session3).Should(Exit(128 + 2))
+			})
+
+			It("should not wait", func() {
+				session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
+				Ω(err).ShouldNot(HaveOccurred())
+
+				Interrupt()
+
+				Ω(session1).ShouldNot(Exit(), "Should not exit immediately...")
+			})
+		})
+	})
+
 	Context("when the command exits", func() {
 		It("should close the buffers", func() {
 			Eventually(session).Should(Exit())
diff --git a/matchers/be_numerically_matcher.go b/matchers/be_numerically_matcher.go
index 52f83fe..0c157f6 100644
--- a/matchers/be_numerically_matcher.go
+++ b/matchers/be_numerically_matcher.go
@@ -2,8 +2,9 @@
 
 import (
 	"fmt"
-	"github.com/onsi/gomega/format"
 	"math"
+
+	"github.com/onsi/gomega/format"
 )
 
 type BeNumericallyMatcher struct {