github.com/onsi/ginkgo@v1.16.6-0.20211118180735-4e1925ba4c95/internal/parallel_support/client_server_test.go (about)

     1  package parallel_support_test
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"time"
     7  
     8  	. "github.com/onsi/ginkgo"
     9  	. "github.com/onsi/gomega"
    10  	"github.com/onsi/gomega/gbytes"
    11  
    12  	"github.com/onsi/ginkgo/internal"
    13  	"github.com/onsi/ginkgo/internal/parallel_support"
    14  	. "github.com/onsi/ginkgo/internal/test_helpers"
    15  	"github.com/onsi/ginkgo/types"
    16  )
    17  
    18  type ColorableStringerStruct struct {
    19  	Label string
    20  	Count int
    21  }
    22  
    23  func (s ColorableStringerStruct) String() string {
    24  	return fmt.Sprintf("%s %d", s.Label, s.Count)
    25  }
    26  
    27  func (s ColorableStringerStruct) ColorableString() string {
    28  	return fmt.Sprintf("{{red}}%s {{green}}%d{{/}}", s.Label, s.Count)
    29  }
    30  
    31  var _ = Describe("The Parallel Support Client & Server", func() {
    32  	for _, protocol := range []string{"RPC", "HTTP"} {
    33  		protocol := protocol
    34  		Describe(fmt.Sprintf("The %s protocol", protocol), Label(protocol), func() {
    35  			var (
    36  				server   parallel_support.Server
    37  				client   parallel_support.Client
    38  				reporter *FakeReporter
    39  				buffer   *gbytes.Buffer
    40  			)
    41  
    42  			BeforeEach(func() {
    43  				GinkgoT().Setenv("GINKGO_PARALLEL_PROTOCOL", protocol)
    44  
    45  				var err error
    46  				reporter = &FakeReporter{}
    47  				server, err = parallel_support.NewServer(3, reporter)
    48  				Ω(err).ShouldNot(HaveOccurred())
    49  				server.Start()
    50  
    51  				buffer = gbytes.NewBuffer()
    52  				server.SetOutputDestination(buffer)
    53  
    54  				client = parallel_support.NewClient(server.Address())
    55  				Eventually(client.Connect).Should(BeTrue())
    56  
    57  				DeferCleanup(server.Close)
    58  				DeferCleanup(client.Close)
    59  			})
    60  
    61  			Describe("Reporting endpoints", func() {
    62  				var beginReport, thirdBeginReport types.Report
    63  				var endReport1, endReport2, endReport3 types.Report
    64  				var specReportA, specReportB, specReportC types.SpecReport
    65  
    66  				var t time.Time
    67  
    68  				BeforeEach(func() {
    69  					beginReport = types.Report{SuiteDescription: "my sweet suite"}
    70  					thirdBeginReport = types.Report{SuiteDescription: "last one in gets forwarded"}
    71  
    72  					specReportA = types.SpecReport{LeafNodeText: "A"}
    73  					specReportB = types.SpecReport{LeafNodeText: "B"}
    74  					specReportC = types.SpecReport{LeafNodeText: "C"}
    75  
    76  					t = time.Now()
    77  
    78  					endReport1 = types.Report{StartTime: t.Add(-time.Second), EndTime: t.Add(time.Second), SuiteSucceeded: true, SpecReports: types.SpecReports{specReportA}}
    79  					endReport2 = types.Report{StartTime: t.Add(-2 * time.Second), EndTime: t.Add(time.Second), SuiteSucceeded: true, SpecReports: types.SpecReports{specReportB}}
    80  					endReport3 = types.Report{StartTime: t.Add(-time.Second), EndTime: t.Add(2 * time.Second), SuiteSucceeded: false, SpecReports: types.SpecReports{specReportC}}
    81  				})
    82  
    83  				Context("before all procs have reported SuiteWillBegin", func() {
    84  					BeforeEach(func() {
    85  						Ω(client.PostSuiteWillBegin(beginReport)).Should(Succeed())
    86  						Ω(client.PostDidRun(specReportA)).Should(Succeed())
    87  						Ω(client.PostSuiteWillBegin(beginReport)).Should(Succeed())
    88  						Ω(client.PostDidRun(specReportB)).Should(Succeed())
    89  					})
    90  
    91  					It("should not forward anything to the attached reporter", func() {
    92  						Ω(reporter.Begin).Should(BeZero())
    93  						Ω(reporter.Will).Should(BeEmpty())
    94  						Ω(reporter.Did).Should(BeEmpty())
    95  					})
    96  
    97  					Context("when the final proc reports SuiteWillBegin", func() {
    98  						BeforeEach(func() {
    99  							Ω(client.PostSuiteWillBegin(thirdBeginReport)).Should(Succeed())
   100  						})
   101  
   102  						It("forwards to SuiteWillBegin and catches up on any received summaries", func() {
   103  							Ω(reporter.Begin).Should(Equal(thirdBeginReport))
   104  							Ω(reporter.Will.Names()).Should(ConsistOf("A", "B"))
   105  							Ω(reporter.Did.Names()).Should(ConsistOf("A", "B"))
   106  						})
   107  
   108  						Context("any subsequent summaries", func() {
   109  							BeforeEach(func() {
   110  								Ω(client.PostDidRun(specReportC)).Should(Succeed())
   111  							})
   112  
   113  							It("are forwarded immediately", func() {
   114  								Ω(reporter.Will.Names()).Should(ConsistOf("A", "B", "C"))
   115  								Ω(reporter.Did.Names()).Should(ConsistOf("A", "B", "C"))
   116  							})
   117  						})
   118  
   119  						Context("when SuiteDidEnd start arriving", func() {
   120  							BeforeEach(func() {
   121  								Ω(client.PostSuiteDidEnd(endReport1)).Should(Succeed())
   122  								Ω(client.PostSuiteDidEnd(endReport2)).Should(Succeed())
   123  							})
   124  
   125  							It("does not forward them yet...", func() {
   126  								Ω(reporter.End).Should(BeZero())
   127  							})
   128  
   129  							It("doesn't signal it's done", func() {
   130  								Ω(server.GetSuiteDone()).ShouldNot(BeClosed())
   131  							})
   132  
   133  							Context("when the final SuiteDidEnd arrive", func() {
   134  								BeforeEach(func() {
   135  									Ω(client.PostSuiteDidEnd(endReport3)).Should(Succeed())
   136  								})
   137  
   138  								It("forwards the aggregation of all received end summaries", func() {
   139  									Ω(reporter.End.StartTime.Unix()).Should(BeNumerically("~", t.Add(-2*time.Second).Unix()))
   140  									Ω(reporter.End.EndTime.Unix()).Should(BeNumerically("~", t.Add(2*time.Second).Unix()))
   141  									Ω(reporter.End.RunTime).Should(BeNumerically("~", 4*time.Second))
   142  									Ω(reporter.End.SuiteSucceeded).Should(BeFalse())
   143  									Ω(reporter.End.SpecReports).Should(ConsistOf(specReportA, specReportB, specReportC))
   144  								})
   145  
   146  								It("should signal it's done", func() {
   147  									Ω(server.GetSuiteDone()).Should(BeClosed())
   148  								})
   149  							})
   150  						})
   151  					})
   152  				})
   153  			})
   154  
   155  			Describe("supporting ReportEntries (which RPC struggled with when I first implemented it)", func() {
   156  				BeforeEach(func() {
   157  					Ω(client.PostSuiteWillBegin(types.Report{SuiteDescription: "my sweet suite"})).Should(Succeed())
   158  					Ω(client.PostSuiteWillBegin(types.Report{SuiteDescription: "my sweet suite"})).Should(Succeed())
   159  					Ω(client.PostSuiteWillBegin(types.Report{SuiteDescription: "my sweet suite"})).Should(Succeed())
   160  				})
   161  				It("can pass in ReportEntries that include custom types", func() {
   162  					cl := types.NewCodeLocation(0)
   163  					entry, err := internal.NewReportEntry("No Value Entry", cl)
   164  					Ω(err).ShouldNot(HaveOccurred())
   165  					Ω(client.PostDidRun(types.SpecReport{
   166  						LeafNodeText:  "no-value",
   167  						ReportEntries: types.ReportEntries{entry},
   168  					})).Should(Succeed())
   169  
   170  					entry, err = internal.NewReportEntry("String Value Entry", cl, "The String")
   171  					Ω(err).ShouldNot(HaveOccurred())
   172  					Ω(client.PostDidRun(types.SpecReport{
   173  						LeafNodeText:  "string-value",
   174  						ReportEntries: types.ReportEntries{entry},
   175  					})).Should(Succeed())
   176  
   177  					entry, err = internal.NewReportEntry("Custom Type Value Entry", cl, ColorableStringerStruct{Label: "apples", Count: 17})
   178  					Ω(err).ShouldNot(HaveOccurred())
   179  					Ω(client.PostDidRun(types.SpecReport{
   180  						LeafNodeText:  "custom-value",
   181  						ReportEntries: types.ReportEntries{entry},
   182  					})).Should(Succeed())
   183  
   184  					Ω(reporter.Did.Find("no-value").ReportEntries[0].Name).Should(Equal("No Value Entry"))
   185  					Ω(reporter.Did.Find("no-value").ReportEntries[0].StringRepresentation()).Should(Equal(""))
   186  
   187  					Ω(reporter.Did.Find("string-value").ReportEntries[0].Name).Should(Equal("String Value Entry"))
   188  					Ω(reporter.Did.Find("string-value").ReportEntries[0].StringRepresentation()).Should(Equal("The String"))
   189  
   190  					Ω(reporter.Did.Find("custom-value").ReportEntries[0].Name).Should(Equal("Custom Type Value Entry"))
   191  					Ω(reporter.Did.Find("custom-value").ReportEntries[0].StringRepresentation()).Should(Equal("{{red}}apples {{green}}17{{/}}"))
   192  				})
   193  			})
   194  
   195  			Describe("Streaming output", func() {
   196  				It("is configured to stream to stdout", func() {
   197  					server, err := parallel_support.NewServer(3, reporter)
   198  					Ω(err).ShouldNot(HaveOccurred())
   199  					Ω(server.GetOutputDestination().(*os.File).Fd()).Should(Equal(uintptr(1)))
   200  				})
   201  
   202  				It("streams output to the provided buffer", func() {
   203  					n, err := client.Write([]byte("hello"))
   204  					Ω(n).Should(Equal(5))
   205  					Ω(err).ShouldNot(HaveOccurred())
   206  					Ω(buffer).Should(gbytes.Say("hello"))
   207  				})
   208  			})
   209  
   210  			Describe("Synchronization endpoints", func() {
   211  				var proc1Exited, proc2Exited, proc3Exited chan interface{}
   212  				BeforeEach(func() {
   213  					proc1Exited, proc2Exited, proc3Exited = make(chan interface{}), make(chan interface{}), make(chan interface{})
   214  					aliveFunc := func(c chan interface{}) func() bool {
   215  						return func() bool {
   216  							select {
   217  							case <-c:
   218  								return false
   219  							default:
   220  								return true
   221  							}
   222  						}
   223  					}
   224  					server.RegisterAlive(1, aliveFunc(proc1Exited))
   225  					server.RegisterAlive(2, aliveFunc(proc2Exited))
   226  					server.RegisterAlive(3, aliveFunc(proc3Exited))
   227  				})
   228  
   229  				Describe("Managing SynchronizedBeforeSuite synchronization", func() {
   230  					Context("when proc 1 succeeds and returns data", func() {
   231  						It("passes that data along to other procs", func() {
   232  							Ω(client.PostSynchronizedBeforeSuiteCompleted(types.SpecStatePassed, []byte("hello there"))).Should(Succeed())
   233  							state, data, err := client.BlockUntilSynchronizedBeforeSuiteData()
   234  							Ω(state).Should(Equal(types.SpecStatePassed))
   235  							Ω(data).Should(Equal([]byte("hello there")))
   236  							Ω(err).ShouldNot(HaveOccurred())
   237  						})
   238  					})
   239  
   240  					Context("when proc 1 succeeds and the data happens to be nil", func() {
   241  						It("passes reports success and returns nil", func() {
   242  							Ω(client.PostSynchronizedBeforeSuiteCompleted(types.SpecStatePassed, nil)).Should(Succeed())
   243  							state, data, err := client.BlockUntilSynchronizedBeforeSuiteData()
   244  							Ω(state).Should(Equal(types.SpecStatePassed))
   245  							Ω(data).Should(BeNil())
   246  							Ω(err).ShouldNot(HaveOccurred())
   247  						})
   248  					})
   249  
   250  					Context("when proc 1 is skipped", func() {
   251  						It("passes that state information along to the other procs", func() {
   252  							Ω(client.PostSynchronizedBeforeSuiteCompleted(types.SpecStateSkipped, nil)).Should(Succeed())
   253  							state, data, err := client.BlockUntilSynchronizedBeforeSuiteData()
   254  							Ω(state).Should(Equal(types.SpecStateSkipped))
   255  							Ω(data).Should(BeNil())
   256  							Ω(err).ShouldNot(HaveOccurred())
   257  						})
   258  					})
   259  
   260  					Context("when proc 1 fails", func() {
   261  						It("passes that state information along to the other procs", func() {
   262  							Ω(client.PostSynchronizedBeforeSuiteCompleted(types.SpecStateFailed, nil)).Should(Succeed())
   263  							state, data, err := client.BlockUntilSynchronizedBeforeSuiteData()
   264  							Ω(state).Should(Equal(types.SpecStateFailed))
   265  							Ω(data).Should(BeNil())
   266  							Ω(err).ShouldNot(HaveOccurred())
   267  						})
   268  					})
   269  
   270  					Context("when proc 1 disappears before reporting back", func() {
   271  						It("returns a meaningful error", func() {
   272  							close(proc1Exited)
   273  							state, data, err := client.BlockUntilSynchronizedBeforeSuiteData()
   274  							Ω(state).Should(Equal(types.SpecStateInvalid))
   275  							Ω(data).Should(BeNil())
   276  							Ω(err).Should(MatchError(types.GinkgoErrors.SynchronizedBeforeSuiteDisappearedOnProc1()))
   277  						})
   278  					})
   279  
   280  					Context("when proc 1 hasn't responded yet", func() {
   281  						It("blocks until it does", func() {
   282  							done := make(chan interface{})
   283  							go func() {
   284  								defer GinkgoRecover()
   285  								state, data, err := client.BlockUntilSynchronizedBeforeSuiteData()
   286  								Ω(state).Should(Equal(types.SpecStatePassed))
   287  								Ω(data).Should(Equal([]byte("hello there")))
   288  								Ω(err).ShouldNot(HaveOccurred())
   289  								close(done)
   290  							}()
   291  							Consistently(done).ShouldNot(BeClosed())
   292  							Ω(client.PostSynchronizedBeforeSuiteCompleted(types.SpecStatePassed, []byte("hello there"))).Should(Succeed())
   293  							Eventually(done).Should(BeClosed())
   294  						})
   295  					})
   296  				})
   297  
   298  				Describe("BlockUntilNonprimaryProcsHaveFinished", func() {
   299  					It("blocks until non-primary procs exit", func() {
   300  						done := make(chan interface{})
   301  						go func() {
   302  							defer GinkgoRecover()
   303  							Ω(client.BlockUntilNonprimaryProcsHaveFinished()).Should(Succeed())
   304  							close(done)
   305  						}()
   306  						Consistently(done).ShouldNot(BeClosed())
   307  						close(proc2Exited)
   308  						Consistently(done).ShouldNot(BeClosed())
   309  						close(proc3Exited)
   310  						Eventually(done).Should(BeClosed())
   311  					})
   312  				})
   313  
   314  				Describe("BlockUntilAggregatedNonprimaryProcsReport", func() {
   315  					var specReportA, specReportB types.SpecReport
   316  					var endReport2, endReport3 types.Report
   317  
   318  					BeforeEach(func() {
   319  						specReportA = types.SpecReport{LeafNodeText: "A"}
   320  						specReportB = types.SpecReport{LeafNodeText: "B"}
   321  						endReport2 = types.Report{SpecReports: types.SpecReports{specReportA}}
   322  						endReport3 = types.Report{SpecReports: types.SpecReports{specReportB}}
   323  					})
   324  
   325  					It("blocks until all non-primary procs exit, then returns the aggregated report", func() {
   326  						done := make(chan interface{})
   327  						go func() {
   328  							defer GinkgoRecover()
   329  							report, err := client.BlockUntilAggregatedNonprimaryProcsReport()
   330  							Ω(err).ShouldNot(HaveOccurred())
   331  							Ω(report.SpecReports).Should(ConsistOf(specReportA, specReportB))
   332  							close(done)
   333  						}()
   334  						Consistently(done).ShouldNot(BeClosed())
   335  
   336  						Ω(client.PostSuiteDidEnd(endReport2)).Should(Succeed())
   337  						close(proc2Exited)
   338  						Consistently(done).ShouldNot(BeClosed())
   339  
   340  						Ω(client.PostSuiteDidEnd(endReport3)).Should(Succeed())
   341  						close(proc3Exited)
   342  						Eventually(done).Should(BeClosed())
   343  					})
   344  
   345  					Context("when a non-primary proc disappears without reporting back", func() {
   346  						It("blocks returns an appropriate error", func() {
   347  							done := make(chan interface{})
   348  							go func() {
   349  								defer GinkgoRecover()
   350  								report, err := client.BlockUntilAggregatedNonprimaryProcsReport()
   351  								Ω(err).Should(Equal(types.GinkgoErrors.AggregatedReportUnavailableDueToNodeDisappearing()))
   352  								Ω(report).Should(BeZero())
   353  								close(done)
   354  							}()
   355  							Consistently(done).ShouldNot(BeClosed())
   356  
   357  							Ω(client.PostSuiteDidEnd(endReport2)).Should(Succeed())
   358  							close(proc2Exited)
   359  							Consistently(done).ShouldNot(BeClosed())
   360  
   361  							close(proc3Exited)
   362  							Eventually(done).Should(BeClosed())
   363  						})
   364  					})
   365  				})
   366  
   367  				Describe("Fetching counters", func() {
   368  					It("returns ascending counters", func() {
   369  						Ω(client.FetchNextCounter()).Should(Equal(0))
   370  						Ω(client.FetchNextCounter()).Should(Equal(1))
   371  						Ω(client.FetchNextCounter()).Should(Equal(2))
   372  						Ω(client.FetchNextCounter()).Should(Equal(3))
   373  					})
   374  				})
   375  
   376  				Describe("Aborting", func() {
   377  					It("should not abort by default", func() {
   378  						Ω(client.ShouldAbort()).Should(BeFalse())
   379  					})
   380  
   381  					Context("when told to abort", func() {
   382  						BeforeEach(func() {
   383  							Ω(client.PostAbort()).Should(Succeed())
   384  						})
   385  
   386  						It("should abort", func() {
   387  							Ω(client.ShouldAbort()).Should(BeTrue())
   388  						})
   389  					})
   390  				})
   391  
   392  			})
   393  		})
   394  	}
   395  })