github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/job/mem_broker_test.go (about) 1 package job_test 2 3 import ( 4 "encoding/json" 5 "strconv" 6 "strings" 7 "sync" 8 "testing" 9 "time" 10 11 "github.com/cozy/cozy-stack/model/job" 12 "github.com/cozy/cozy-stack/pkg/config/config" 13 "github.com/cozy/cozy-stack/pkg/limits" 14 "github.com/cozy/cozy-stack/pkg/prefixer" 15 "github.com/cozy/cozy-stack/tests/testutils" 16 "github.com/stretchr/testify/assert" 17 ) 18 19 func TestMemBroker(t *testing.T) { 20 if testing.Short() { 21 t.Skip("an instance is required for this test: test skipped due to the use of --short flag") 22 } 23 24 config.UseTestFile(t) 25 setup := testutils.NewSetup(t, t.Name()) 26 testInstance := setup.GetTestInstance() 27 28 t.Run("ProperSerial", func(t *testing.T) { 29 j := job.NewJob(prefixer.NewPrefixer(0, "cozy.localhost:8080", "cozy.localhost:8080"), 30 &job.JobRequest{ 31 WorkerType: "", 32 }) 33 assert.NoError(t, j.Create()) 34 assert.NoError(t, j.AckConsumed()) 35 job2, err := job.Get(j, j.ID()) 36 assert.NoError(t, err) 37 assert.Equal(t, job.Running, job2.State) 38 }) 39 40 t.Run("MessageMarshalling", func(t *testing.T) { 41 data := []byte(`{"Data": "InZhbHVlIgo=", "Type": "json"}`) 42 var m job.Message 43 assert.NoError(t, json.Unmarshal(data, &m)) 44 var s string 45 assert.NoError(t, m.Unmarshal(&s)) 46 assert.Equal(t, "value", s) 47 48 data = []byte(`"value2"`) 49 assert.NoError(t, json.Unmarshal(data, &m)) 50 assert.NoError(t, m.Unmarshal(&s)) 51 assert.Equal(t, "value2", s) 52 53 data = []byte(`{ 54 "domain": "cozy.local", 55 "worker": "foo", 56 "message": {"Data": "InZhbHVlIgo=", "Type": "json"} 57 }`) 58 59 var j job.Job 60 assert.NoError(t, json.Unmarshal(data, &j)) 61 assert.Equal(t, "cozy.local", j.Domain) 62 assert.Equal(t, "foo", j.WorkerType) 63 assert.EqualValues(t, []byte(`"value"`), j.Message) 64 65 var err error 66 var j2 job.Job 67 data, err = json.Marshal(j) 68 assert.NoError(t, err) 69 assert.NoError(t, json.Unmarshal(data, &j2)) 70 assert.Equal(t, "cozy.local", j2.Domain) 71 assert.Equal(t, "foo", j2.WorkerType) 72 assert.EqualValues(t, []byte(`"value"`), j2.Message) 73 74 assert.EqualValues(t, &j2, j2.Clone()) 75 }) 76 77 t.Run("InMemoryJobs", func(t *testing.T) { 78 n := 10 79 v := 100 80 81 var w sync.WaitGroup 82 83 workersTestList := job.WorkersList{ 84 { 85 WorkerType: "test", 86 Concurrency: 4, 87 WorkerFunc: func(ctx *job.TaskContext) error { 88 var msg string 89 err := ctx.UnmarshalMessage(&msg) 90 if !assert.NoError(t, err) { 91 return err 92 } 93 if strings.HasPrefix(msg, "a-") { 94 _, err := strconv.Atoi(msg[len("a-"):]) 95 assert.NoError(t, err) 96 } else if strings.HasPrefix(msg, "b-") { 97 _, err := strconv.Atoi(msg[len("b-"):]) 98 assert.NoError(t, err) 99 } else { 100 t.Fatal() 101 } 102 w.Done() 103 return nil 104 }, 105 }, 106 } 107 108 broker1 := job.NewMemBroker() 109 broker2 := job.NewMemBroker() 110 assert.NoError(t, broker1.StartWorkers(workersTestList)) 111 assert.NoError(t, broker2.StartWorkers(workersTestList)) 112 w.Add(2) 113 114 go func() { 115 for i := 0; i < n; i++ { 116 w.Add(1) 117 msg, _ := job.NewMessage("a-" + strconv.Itoa(i+1)) 118 _, err := broker1.PushJob(testInstance, &job.JobRequest{ 119 WorkerType: "test", 120 Message: msg, 121 }) 122 assert.NoError(t, err) 123 time.Sleep(randomMicro(0, v)) 124 } 125 w.Done() 126 }() 127 128 go func() { 129 for i := 0; i < n; i++ { 130 w.Add(1) 131 msg, _ := job.NewMessage("b-" + strconv.Itoa(i+1)) 132 _, err := broker2.PushJob(testInstance, &job.JobRequest{ 133 WorkerType: "test", 134 Message: msg, 135 }) 136 assert.NoError(t, err) 137 time.Sleep(randomMicro(0, v)) 138 } 139 w.Done() 140 }() 141 142 w.Wait() 143 }) 144 145 t.Run("UnknownWorkerError", func(t *testing.T) { 146 broker := job.NewMemBroker() 147 assert.NoError(t, broker.StartWorkers(job.WorkersList{})) 148 _, err := broker.PushJob(testInstance, &job.JobRequest{ 149 WorkerType: "nope", 150 Message: nil, 151 }) 152 assert.Error(t, err) 153 assert.Equal(t, job.ErrUnknownWorker, err) 154 }) 155 156 t.Run("UnknownMessageType", func(t *testing.T) { 157 var w sync.WaitGroup 158 159 broker := job.NewMemBroker() 160 assert.NoError(t, broker.StartWorkers(job.WorkersList{ 161 { 162 WorkerType: "test", 163 Concurrency: 4, 164 WorkerFunc: func(ctx *job.TaskContext) error { 165 var msg string 166 err := ctx.UnmarshalMessage(&msg) 167 assert.Error(t, err) 168 assert.Equal(t, job.ErrMessageNil, err) 169 w.Done() 170 return nil 171 }, 172 }, 173 })) 174 175 w.Add(1) 176 _, err := broker.PushJob(testInstance, &job.JobRequest{ 177 WorkerType: "test", 178 Message: nil, 179 }) 180 181 assert.NoError(t, err) 182 w.Wait() 183 }) 184 185 t.Run("Timeout", func(t *testing.T) { 186 var w sync.WaitGroup 187 188 broker := job.NewMemBroker() 189 assert.NoError(t, broker.StartWorkers(job.WorkersList{ 190 { 191 WorkerType: "timeout", 192 Concurrency: 1, 193 MaxExecCount: 1, 194 Timeout: 1 * time.Millisecond, 195 WorkerFunc: func(ctx *job.TaskContext) error { 196 <-ctx.Done() 197 w.Done() 198 return ctx.Err() 199 }, 200 }, 201 })) 202 203 w.Add(1) 204 _, err := broker.PushJob(testInstance, &job.JobRequest{ 205 WorkerType: "timeout", 206 Message: nil, 207 }) 208 209 assert.NoError(t, err) 210 w.Wait() 211 }) 212 213 t.Run("Retry", func(t *testing.T) { 214 var w sync.WaitGroup 215 216 maxExecCount := 4 217 218 var count int 219 broker := job.NewMemBroker() 220 assert.NoError(t, broker.StartWorkers(job.WorkersList{ 221 { 222 WorkerType: "test", 223 Concurrency: 1, 224 MaxExecCount: maxExecCount, 225 Timeout: 1 * time.Millisecond, 226 RetryDelay: 1 * time.Millisecond, 227 WorkerFunc: func(ctx *job.TaskContext) error { 228 <-ctx.Done() 229 w.Done() 230 count++ 231 if count < maxExecCount { 232 return ctx.Err() 233 } 234 return nil 235 }, 236 }, 237 })) 238 239 w.Add(maxExecCount) 240 _, err := broker.PushJob(testInstance, &job.JobRequest{ 241 WorkerType: "test", 242 Message: nil, 243 }) 244 245 assert.NoError(t, err) 246 w.Wait() 247 }) 248 249 t.Run("PanicRetried", func(t *testing.T) { 250 var w sync.WaitGroup 251 252 maxExecCount := 4 253 254 broker := job.NewMemBroker() 255 assert.NoError(t, broker.StartWorkers(job.WorkersList{ 256 { 257 WorkerType: "panic", 258 Concurrency: 1, 259 MaxExecCount: maxExecCount, 260 RetryDelay: 1 * time.Millisecond, 261 WorkerFunc: func(ctx *job.TaskContext) error { 262 w.Done() 263 panic("oops") 264 }, 265 }, 266 })) 267 268 w.Add(maxExecCount) 269 _, err := broker.PushJob(testInstance, &job.JobRequest{ 270 WorkerType: "panic", 271 Message: nil, 272 }) 273 274 assert.NoError(t, err) 275 w.Wait() 276 }) 277 278 t.Run("Panic", func(t *testing.T) { 279 var w sync.WaitGroup 280 281 even, _ := job.NewMessage(0) 282 odd, _ := job.NewMessage(1) 283 284 broker := job.NewMemBroker() 285 assert.NoError(t, broker.StartWorkers(job.WorkersList{ 286 { 287 WorkerType: "panic2", 288 Concurrency: 1, 289 MaxExecCount: 1, 290 RetryDelay: 1 * time.Millisecond, 291 WorkerFunc: func(ctx *job.TaskContext) error { 292 var i int 293 if err := ctx.UnmarshalMessage(&i); err != nil { 294 return err 295 } 296 if i%2 != 0 { 297 panic("oops") 298 } 299 w.Done() 300 return nil 301 }, 302 }, 303 })) 304 w.Add(2) 305 var err error 306 _, err = broker.PushJob(testInstance, &job.JobRequest{WorkerType: "panic2", Message: odd}) 307 assert.NoError(t, err) 308 _, err = broker.PushJob(testInstance, &job.JobRequest{WorkerType: "panic2", Message: even}) 309 assert.NoError(t, err) 310 _, err = broker.PushJob(testInstance, &job.JobRequest{WorkerType: "panic2", Message: odd}) 311 assert.NoError(t, err) 312 _, err = broker.PushJob(testInstance, &job.JobRequest{WorkerType: "panic2", Message: even}) 313 assert.NoError(t, err) 314 w.Wait() 315 }) 316 317 t.Run("MemAddJobRateLimitExceeded", func(t *testing.T) { 318 workersTestList := job.WorkersList{ 319 { 320 WorkerType: "thumbnail", 321 Concurrency: 4, 322 WorkerFunc: func(ctx *job.TaskContext) error { 323 return nil 324 }, 325 }, 326 } 327 broker := job.NewMemBroker() 328 err := broker.StartWorkers(workersTestList) 329 assert.NoError(t, err) 330 331 msg, _ := job.NewMessage("z-0") 332 j, err := broker.PushJob(testInstance, &job.JobRequest{ 333 WorkerType: "thumbnail", 334 Message: msg, 335 }) 336 337 assert.NoError(t, err) 338 assert.NotNil(t, j) 339 340 ct := limits.JobThumbnailType 341 limits.SetMaximumLimit(ct, 10) 342 maxLimit := limits.GetMaximumLimit(ct) 343 // Blocking the job push 344 for i := int64(0); i < maxLimit-1; i++ { 345 j, err := broker.PushJob(testInstance, &job.JobRequest{ 346 WorkerType: "thumbnail", 347 Message: msg, 348 }) 349 assert.NoError(t, err) 350 assert.NotNil(t, j) 351 } 352 353 j, err = broker.PushJob(testInstance, &job.JobRequest{ 354 WorkerType: "thumbnail", 355 Message: msg, 356 }) 357 assert.Error(t, err) 358 assert.Nil(t, j) 359 assert.ErrorIs(t, err, limits.ErrRateLimitReached) 360 361 j, err = broker.PushJob(testInstance, &job.JobRequest{ 362 WorkerType: "thumbnail", 363 Message: msg, 364 }) 365 assert.Error(t, err) 366 assert.Nil(t, j) 367 }) 368 }