go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/common/pubsub/pump_test.go (about) 1 // Copyright 2021 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package pubsub 16 17 import ( 18 "context" 19 "fmt" 20 "testing" 21 "time" 22 23 "cloud.google.com/go/pubsub" 24 25 "go.chromium.org/luci/common/data/stringset" 26 "go.chromium.org/luci/common/retry/transient" 27 "go.chromium.org/luci/cv/internal/cvtesting" 28 29 . "github.com/smartystreets/goconvey/convey" 30 ) 31 32 func TestPullingBatchProcessor(t *testing.T) { 33 t.Parallel() 34 Convey("PBP works ", t, func() { 35 ct := cvtesting.Test{MaxDuration: time.Minute} 36 ctx, cancel := ct.SetUp(t) 37 defer cancel() 38 39 psSrv, err := NewTestPSServer(ctx) 40 defer func() { _ = psSrv.Close() }() 41 So(err, ShouldBeNil) 42 43 processed := make(chan string, 100) 44 defer close(processed) 45 46 // Use this to inject errors. 47 batchErrChan := make(chan error, 1) 48 defer close(batchErrChan) 49 50 pump := &PullingBatchProcessor{ 51 ProcessBatch: makeTestProcessBatch(processed, batchErrChan), 52 ProjectID: psSrv.ProjID, 53 SubID: psSrv.SubID, 54 } 55 56 Convey("non-erroring payload", func() { 57 Convey("with concurrent batches", func() { 58 pump.Options.ConcurrentBatches = 5 59 }) 60 Convey("without concurrent batches", func() { 61 pump.Options.ConcurrentBatches = 1 62 }) 63 So(pump.Validate(), ShouldBeNil) 64 nMessages := pump.Options.MaxBatchSize + 1 65 66 sent := psSrv.PublishTestMessages(nMessages) 67 So(sent, ShouldHaveLength, nMessages) 68 expected := len(sent) 69 received := make(stringset.Set, len(sent)) 70 71 processCtx, cancelProcess := context.WithCancel(ctx) 72 done := runProcessAsync(processCtx, psSrv.Client, pump.process) 73 for len(received) < expected { 74 select { 75 case <-ctx.Done(): 76 panic(fmt.Errorf("expected %d messages, only received %d after too long", expected, len(received))) 77 case msg := <-processed: 78 received.Add(msg) 79 } 80 } 81 82 // There should be no more messages in processed. 83 select { 84 case msg := <-processed: 85 panic(fmt.Sprintf("This test fails because there should be no more messages in this channel, but there was %q", msg)) 86 default: 87 } 88 cancelProcess() 89 So(<-done, ShouldBeNil) 90 So(received, ShouldResemble, sent) 91 }) 92 93 Convey("With error", func() { 94 // Configure the pump to handle one message at a time s.t. a single 95 // batch results in the injected error. 96 pump.Options.MaxBatchSize = 1 97 pump.Options.ConcurrentBatches = 1 98 So(pump.Validate(), ShouldBeNil) 99 100 var processingError error 101 var deliveriesExpected, processedMessagesExpected int 102 var processErrorAssertion, sentVSReceivedAssertion func(actual any, expected ...any) string 103 Convey("permanent", func() { 104 processingError = fmt.Errorf("non-transient error") 105 // Each of the messages should be delivered at least once. 106 deliveriesExpected = 2 107 // The error should be surfaced by .Process() 108 processErrorAssertion = ShouldBeError 109 110 processedMessagesExpected = 1 111 112 sentVSReceivedAssertion = ShouldNotResemble 113 }) 114 Convey("transient", func() { 115 processingError = transient.Tag.Apply(fmt.Errorf("transient error")) 116 // The message that failed transiently should be re-delivered at least once. 117 deliveriesExpected = 3 118 // The error should not be surfaced by .Process() 119 processErrorAssertion = ShouldBeNil 120 121 processedMessagesExpected = 2 122 123 sentVSReceivedAssertion = ShouldResemble 124 }) 125 126 // Publish two messages. 127 sent := psSrv.PublishTestMessages(2) 128 // Inject error to be returned by the processing of the first 129 // batch-of-one-message. 130 batchErrChan <- processingError 131 132 // Run the pump. 133 processCtx, cancelProcess := context.WithCancel(ctx) 134 done := runProcessAsync(processCtx, psSrv.Client, pump.process) 135 136 // Receive exactly as many distinct messages as expected. 137 received := make(stringset.Set, processedMessagesExpected) 138 for len(received) < processedMessagesExpected { 139 select { 140 case <-ctx.Done(): 141 panic(fmt.Errorf("expected %d messages, only received %d after too long", processedMessagesExpected, len(received))) 142 case msg := <-processed: 143 received.Add(msg) 144 } 145 } 146 cancelProcess() 147 148 // Ensure there were at least as many message deliveries as expected. 149 actualDeliveries := 0 150 for _, m := range psSrv.Messages() { 151 actualDeliveries += m.Deliveries 152 } 153 So(actualDeliveries, ShouldBeGreaterThanOrEqualTo, deliveriesExpected) 154 155 // Compare sent and received messages. 156 So(sent, sentVSReceivedAssertion, received) 157 158 // Check that the pump surfaced (or not) the error, as appropriate. 159 So(<-done, processErrorAssertion) 160 }) 161 162 Convey("Can enforce runTime", func() { 163 // Publish a lot of messages 164 sent := psSrv.PublishTestMessages(100) 165 received := make(stringset.Set, len(sent)) 166 167 pump.Options.MaxBatchSize = 1 168 pump.Options.ConcurrentBatches = 1 169 pump.ProcessBatch = func(ctx context.Context, msgs []*pubsub.Message) error { 170 // As soon as the first message is received, move the clock 171 // past the runTime deadline. 172 ct.Clock.Add(pump.Options.ReceiveDuration) 173 for _, msg := range msgs { 174 received.Add(string(msg.Data)) 175 } 176 return nil 177 } 178 So(pump.Validate(), ShouldBeNil) 179 180 done := runProcessAsync(ctx, psSrv.Client, pump.process) 181 182 select { 183 case <-ctx.Done(): 184 panic("This test failed because .Process() did not respect .runTime") 185 case err := <-done: 186 So(err, ShouldBeNil) 187 } 188 So(len(received), ShouldBeLessThan, len(sent)) 189 }) 190 }) 191 } 192 193 func runProcessAsync(ctx context.Context, client *pubsub.Client, payload func(context.Context, *pubsub.Client) error) <-chan error { 194 done := make(chan error, 1) 195 go func() { 196 defer close(done) 197 done <- payload(ctx, client) 198 }() 199 return done 200 } 201 202 // makeTestProcessBatch returns a trivial batch processing function (a closure). 203 // 204 // It puts every message in the given output channel, and if an error is 205 // available at batchErrChan, returns that instead of nil. 206 func makeTestProcessBatch(processed chan<- string, batchErrChan <-chan error) func(ctx context.Context, msgs []*pubsub.Message) error { 207 return func(ctx context.Context, msgs []*pubsub.Message) error { 208 209 select { 210 case err := <-batchErrChan: 211 return err 212 default: 213 214 } 215 for _, msg := range msgs { 216 processed <- string(msg.Data) 217 } 218 return nil 219 } 220 }