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 })