go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/sync/parallel/runner_test.go (about) 1 // Copyright 2015 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 parallel 16 17 import ( 18 "errors" 19 "fmt" 20 "sync/atomic" 21 "testing" 22 23 . "github.com/smartystreets/goconvey/convey" 24 . "go.chromium.org/luci/common/testing/assertions" 25 ) 26 27 type numberError int 28 29 func (e numberError) Error() string { 30 return fmt.Sprintf("#%d", e) 31 } 32 33 func TestRunner(t *testing.T) { 34 t.Parallel() 35 36 Convey("When using a Runner directly", t, func() { 37 r := &Runner{} 38 defer func() { 39 if r != nil { 40 r.Close() 41 } 42 }() 43 44 Convey(`Can schedule individual tasks.`, func() { 45 const iters = 100 46 47 ac := int32(0) 48 resultC := make(chan int) 49 50 // Dispatch iters tasks. 51 for i := 0; i < iters; i++ { 52 i := i 53 54 errC := r.RunOne(func() error { 55 atomic.AddInt32(&ac, int32(i)) 56 return numberError(i) 57 }) 58 59 // Reap errC. 60 go func() { 61 resultC <- int((<-errC).(numberError)) 62 }() 63 } 64 65 // Reap the results and compare. 66 result := 0 67 for i := 0; i < iters; i++ { 68 result += <-resultC 69 } 70 So(result, ShouldEqual, atomic.LoadInt32(&ac)) 71 }) 72 73 Convey(`Can use WorkC directly in a bidirectional select loop.`, func() { 74 // Generate a function that writes a value to "outC". 75 outC := make(chan int) 76 valueWriter := func(v int) func() error { 77 return func() error { 78 outC <- v 79 return nil 80 } 81 } 82 83 // We will repeatedly dispatch tasks and reap their results in the same 84 // select loop. 85 // 86 // remaining is the total number of tasks to dispatch. dispatch controls 87 // the number of simultaneous tasks that we're willing to send at a time. 88 // count is the number of tasks that have completed. 89 remaining := 1000 90 count := 0 91 dispatch := 10 92 wc := r.WorkC() 93 94 for count < 1000 { 95 select { 96 case wc <- WorkItem{F: valueWriter(1)}: 97 dispatch-- 98 remaining-- 99 break 100 101 case v := <-outC: 102 count += v 103 dispatch++ 104 } 105 106 if dispatch == 0 || remaining == 0 { 107 // Stop writing work items until we reap some results. 108 wc = nil 109 } else { 110 // We have results, start writing work items again. 111 wc = r.WorkC() 112 } 113 } 114 115 So(count, ShouldEqual, 1000) 116 }) 117 118 Convey(`A WorkItem's After method can recover from a panic.`, func() { 119 testErr := errors.New("test error") 120 121 var err error 122 r.WorkC() <- WorkItem{ 123 F: func() error { 124 panic(testErr) 125 }, 126 After: func() { 127 if r := recover(); r != nil { 128 err = r.(error) 129 } 130 }, 131 } 132 133 r.Close() 134 r = nil // Do we don't close it in a defer. 135 136 So(err, ShouldEqual, testErr) 137 }) 138 139 Convey("Ignore consumes the errors and blocks", func() { 140 count := new(int32) 141 Ignore(r.Run(func(ch chan<- func() error) { 142 for i := 0; i < 100; i++ { 143 ch <- func() error { 144 atomic.AddInt32(count, 1) 145 return fmt.Errorf("whaaattt") 146 } 147 } 148 })) 149 So(*count, ShouldEqual, 100) 150 }) 151 152 Convey("Must panics on the first error", func() { 153 r.Maximum = 1 154 count := new(int32) 155 156 // Reap errC at the end, since Must will panic without consuming its 157 // contents. 158 var errC <-chan error 159 defer func() { 160 if errC != nil { 161 for range errC { 162 } 163 } 164 }() 165 166 So(func() { 167 errC = r.Run(func(ch chan<- func() error) { 168 for i := 0; i < 100; i++ { 169 i := i 170 ch <- func() error { 171 atomic.AddInt32(count, 1) 172 return fmt.Errorf("whaaattt: %d", i) 173 } 174 } 175 }) 176 Must(errC) 177 }, ShouldPanicLike, "whaaattt: 0") 178 // Either: 179 // * the panic happened and we load count before ch is unblocked 180 // * the panic happened then ch(1) pushes and runs, then we load count 181 // So count will either be 1 or 2, but never more or less. 182 So(atomic.LoadInt32(count), ShouldBeBetweenOrEqual, 1, 2) 183 }) 184 }) 185 }