Merge branch 'master' into kill-rogue-processes
diff --git a/gexec/session.go b/gexec/session.go
index 46e7122..1dd5068 100644
--- a/gexec/session.go
+++ b/gexec/session.go
@@ -94,6 +94,7 @@
 		go session.monitorForExit(exited)
 	}
 
+	trackedSessions = append(trackedSessions, session)
 	return session, err
 }
 
@@ -179,7 +180,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 +213,90 @@
 
 	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...)
+	}
+}
+
+/*
+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..5a7af74 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,140 @@
 		})
 	})
 
+	Context("tracking sessions", func() {
+		BeforeEach(func() {
+			KillAndWait()
+		})
+
+		Describe("kill", func() {
+			It("should kill 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())
+
+				Kill()
+				Ω(session1).ShouldNot(Exit(), "Should not exit immediately...")
+				Ω(session2).ShouldNot(Exit(), "Should not exit immediately...")
+				Ω(session3).ShouldNot(Exit(), "Should not exit immediately...")
+
+				Eventually(session1).Should(Exit(128 + 9))
+				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, 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())
+
+				Terminate()
+
+				Ω(session1).ShouldNot(Exit(), "Should not exit immediately...")
+				Ω(session2).ShouldNot(Exit(), "Should not exit immediately...")
+				Ω(session3).ShouldNot(Exit(), "Should not exit immediately...")
+
+				Eventually(session1).Should(Exit(128 + 15))
+				Eventually(session2).Should(Exit(128 + 15))
+				Eventually(session3).Should(Exit(128 + 15))
+			})
+		})
+
+		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, 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())
+
+				Signal(syscall.SIGABRT)
+
+				Ω(session1).ShouldNot(Exit(), "Should not exit immediately...")
+				Ω(session2).ShouldNot(Exit(), "Should not exit immediately...")
+				Ω(session3).ShouldNot(Exit(), "Should not exit immediately...")
+
+				Eventually(session1).Should(Exit(128 + 6))
+				Eventually(session2).Should(Exit(128 + 6))
+				Eventually(session3).Should(Exit(128 + 6))
+			})
+		})
+
+		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()
+
+				Ω(session1).ShouldNot(Exit(), "Should not exit immediately...")
+				Ω(session2).ShouldNot(Exit(), "Should not exit immediately...")
+				Ω(session3).ShouldNot(Exit(), "Should not exit immediately...")
+
+				Eventually(session1).Should(Exit(128 + 2))
+				Eventually(session2).Should(Exit(128 + 2))
+				Eventually(session3).Should(Exit(128 + 2))
+			})
+		})
+	})
+
 	Context("when the command exits", func() {
 		It("should close the buffers", func() {
 			Eventually(session).Should(Exit())