package integration_test import ( "encoding/json" "os/exec" "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" ) var _ = Describe("Interrupt and Timeout", func() { Context("when interrupting a suite", func() { It("gives the user feedback as the session is interrupted", func() { fm.MountFixture("hanging") //we need to signal the actual process, so we must compile the test first session := startGinkgo(fm.PathTo("hanging"), "build") Eventually(session).Should(gexec.Exit(0)) //then run the compiled test directly cmd := exec.Command("./hanging.test", "--test.v", "--ginkgo.no-color", "--ginkgo.grace-period=2s") cmd.Dir = fm.PathTo("hanging") var err error session, err = gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Ω(err).ShouldNot(HaveOccurred()) Eventually(session).Should(gbytes.Say("Sleeping...")) session.Interrupt() Eventually(session).Should(gbytes.Say(`First interrupt received`)) Eventually(session).Should(gbytes.Say("Begin Captured GinkgoWriter Output")) Eventually(session).Should(gbytes.Say("Just beginning")) Eventually(session).Should(gbytes.Say("Almost there...")) Eventually(session).Should(gbytes.Say("Hanging Out")) Eventually(session).Should(gbytes.Say(`goroutine \d+ \[select\]`)) Eventually(session).Should(gbytes.Say(`>\s*select {`), "The actual source code gets emitted") Eventually(session, time.Second*5).Should(gbytes.Say("Cleaning up once..."), "The two second grace period should move on past the napping node") Eventually(session).Should(gbytes.Say("Cleaning up twice...")) Eventually(session).Should(gbytes.Say("Sleeping again...")) session.Interrupt() Eventually(session).Should(gbytes.Say(`Second interrupt received`)) Eventually(session).Should(gbytes.Say(`goroutine \d+ \[sleep\]`)) Eventually(session).Should(gbytes.Say(`>\s*time.Sleep\(time.Hour\)`), "The actual source code gets emitted now") Eventually(session).Should(gbytes.Say(`\[INTERRUPTED\]`)) Eventually(session).Should(gbytes.Say(`Interrupted by User`)) Eventually(session).Should(gbytes.Say(`Spec Goroutine`)) Eventually(session).Should(gbytes.Say(`goroutine \d+ \[select\]`)) Eventually(session).Should(gbytes.Say(`>\s*select {`), "The actual source code gets emitted now") Eventually(session).Should(gbytes.Say(`Other Goroutines`)) Eventually(session).Should(gbytes.Say(`main\.main\(\)`)) Eventually(session).Should(gbytes.Say("Reporting at the end")) Eventually(session).Should(gbytes.Say(`FAIL! - Interrupted by User`)) Eventually(session, time.Second*10).Should(gexec.Exit(1)) // the last AfterEach and the AfterSuite don't actually run Ω(string(session.Out.Contents())).ShouldNot(ContainSubstring("Cleaning up thrice")) Ω(string(session.Out.Contents())).ShouldNot(ContainSubstring("Heading Out After Suite")) }) }) Context("when the suite times out", func() { It("interrupts the suite and gives the user feedback as it does so", func() { fm.MountFixture("hanging") session := startGinkgo(fm.PathTo("hanging"), "--no-color", "--timeout=5s", "--grace-period=1s") Eventually(session, time.Second*10).Should(gexec.Exit(1)) Ω(session).Should(gbytes.Say("Sleeping...")) Ω(session).Should(gbytes.Say("Got your signal, but still taking a nap"), "the timeout has signaled the it to stop, but it's napping...") Ω(session).Should(gbytes.Say("A running node failed to exit in time"), "so we forcibly casue it to exit after the grace-period elapses") Ω(session).Should(gbytes.Say("Cleaning up once...")) Ω(session).Should(gbytes.Say("Cleaning up twice...")) Ω(session).Should(gbytes.Say("Cleaning up thrice..."), "we manage to get here even though the second after-each gets stuck. that's thanks to the GracePeriod configuration.") Ω(session).Should(gbytes.Say(`\[TIMEDOUT\]`)) Ω(session).Should(gbytes.Say(`Spec Goroutine`)) Ω(session).Should(gbytes.Say(`goroutine \d+ \[select\]`)) Ω(session).Should(gbytes.Say(`>\s*select {`), "The actual source code gets emitted now") Ω(session).ShouldNot(gbytes.Say(`Other Goroutines`)) Ω(session).Should(gbytes.Say("FAIL! - Suite Timeout Elapsed")) }) }) Describe("applying the timeout to multiple suites", func() { It("tracks the timeout across the suites, decrementing the available timeout for each individual suite, and reports on any suites that did not run because the timeout elapsed", Label("slow"), func() { fm.MountFixture("timeout") session := startGinkgo(fm.PathTo("timeout"), "--no-color", "-r", "--timeout=10s", "--keep-going", "--json-report=out.json") Eventually(session).Should(gbytes.Say("TimeoutA Suite")) Eventually(session, "15s").Should(gexec.Exit(1)) Ω(session).Should(gbytes.Say(`timeout_D ./timeout_D \[Suite did not run because the timeout elapsed\]`)) data := []byte(fm.ContentOf("timeout", "out.json")) reports := []types.Report{} Ω(json.Unmarshal(data, &reports)).Should(Succeed()) Ω(reports[3].SpecialSuiteFailureReasons).Should(ContainElement("Suite did not run because the timeout elapsed")) }) }) })