go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/impl/memory/taskqueue_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 memory 16 17 import ( 18 "context" 19 "fmt" 20 "math/rand" 21 "net/http" 22 "testing" 23 "time" 24 25 "go.chromium.org/luci/common/clock" 26 "go.chromium.org/luci/common/clock/testclock" 27 "go.chromium.org/luci/common/data/rand/mathrand" 28 29 ds "go.chromium.org/luci/gae/service/datastore" 30 "go.chromium.org/luci/gae/service/info" 31 tq "go.chromium.org/luci/gae/service/taskqueue" 32 33 . "github.com/smartystreets/goconvey/convey" 34 . "go.chromium.org/luci/common/testing/assertions" 35 ) 36 37 func TestTaskQueue(t *testing.T) { 38 t.Parallel() 39 40 Convey("TaskQueue", t, func() { 41 now := time.Date(2000, time.January, 1, 1, 1, 1, 1, time.UTC) 42 c, tc := testclock.UseTime(context.Background(), now) 43 c = mathrand.Set(c, rand.New(rand.NewSource(clock.Now(c).UnixNano()))) 44 c = Use(c) 45 46 tqt := tq.GetTestable(c) 47 So(tqt, ShouldNotBeNil) 48 49 So(tq.Raw(c), ShouldNotBeNil) 50 51 Convey("implements TQMultiReadWriter", func() { 52 Convey("Add", func() { 53 t := &tq.Task{Path: "/hello/world"} 54 55 Convey("works", func() { 56 t.Delay = 4 * time.Second 57 t.Header = http.Header{} 58 t.Header.Add("Cat", "tabby") 59 t.Payload = []byte("watwatwat") 60 t.RetryOptions = &tq.RetryOptions{AgeLimit: 7 * time.Second} 61 So(tq.Add(c, "", t), ShouldBeNil) 62 63 var scheduled *tq.Task 64 for _, t := range tqt.GetScheduledTasks()["default"] { 65 scheduled = t 66 break 67 } 68 So(scheduled, ShouldResemble, &tq.Task{ 69 ETA: now.Add(4 * time.Second), 70 Header: http.Header{"Cat": []string{"tabby"}}, 71 Method: "POST", 72 Name: "16045561405319332057", 73 Path: "/hello/world", 74 Payload: []byte("watwatwat"), 75 RetryOptions: &tq.RetryOptions{AgeLimit: 7 * time.Second}, 76 }) 77 }) 78 79 Convey("picks up namespace", func() { 80 c, err := info.Namespace(c, "coolNamespace") 81 So(err, ShouldBeNil) 82 83 t := &tq.Task{} 84 So(tq.Add(c, "", t), ShouldBeNil) 85 So(t.Header, ShouldResemble, http.Header{ 86 "X-Appengine-Current-Namespace": {"coolNamespace"}, 87 }) 88 89 Convey("namespaced Testable only returns tasks for that namespace", func() { 90 So(tq.GetTestable(c).GetScheduledTasks()["default"], ShouldHaveLength, 1) 91 92 // Default namespace has no tasks in its queue. 93 So(tq.GetTestable(info.MustNamespace(c, "")).GetScheduledTasks()["default"], ShouldHaveLength, 0) 94 }) 95 }) 96 97 Convey("cannot add to bad queues", func() { 98 So(tq.Add(c, "waaat", &tq.Task{}).Error(), ShouldContainSubstring, "UNKNOWN_QUEUE") 99 100 Convey("but you can add Queues when testing", func() { 101 tqt.CreateQueue("waaat") 102 So(tq.Add(c, "waaat", t), ShouldBeNil) 103 104 Convey("you just can't add them twice", func() { 105 So(func() { tqt.CreateQueue("waaat") }, ShouldPanic) 106 }) 107 }) 108 }) 109 110 Convey("supplies a URL if it's missing", func() { 111 t.Path = "" 112 So(tq.Add(c, "", t), ShouldBeNil) 113 So(t.Path, ShouldEqual, "/_ah/queue/default") 114 }) 115 116 Convey("cannot add twice", func() { 117 t.Name = "bob" 118 So(tq.Add(c, "", t), ShouldBeNil) 119 120 // can't add the same one twice! 121 So(tq.Add(c, "", t), ShouldEqual, tq.ErrTaskAlreadyAdded) 122 }) 123 124 Convey("cannot add deleted task", func() { 125 t.Name = "bob" 126 So(tq.Add(c, "", t), ShouldBeNil) 127 128 So(tq.Delete(c, "", t), ShouldBeNil) 129 130 // can't add a deleted task! 131 So(tq.Add(c, "", t), ShouldEqual, tq.ErrTaskAlreadyAdded) 132 }) 133 134 Convey("must use a reasonable method", func() { 135 t.Method = "Crystal" 136 So(tq.Add(c, "", t).Error(), ShouldContainSubstring, "bad method") 137 }) 138 139 Convey("payload gets dumped for non POST/PUT methods", func() { 140 t.Method = "HEAD" 141 t.Payload = []byte("coool") 142 So(tq.Add(c, "", t), ShouldBeNil) 143 So(t.Payload, ShouldBeNil) 144 }) 145 146 Convey("invalid names are rejected", func() { 147 t.Name = "happy times" 148 So(tq.Add(c, "", t).Error(), ShouldContainSubstring, "INVALID_TASK_NAME") 149 }) 150 151 Convey("AddMulti also works", func() { 152 t2 := t.Duplicate() 153 t2.Path = "/hi/city" 154 155 expect := []*tq.Task{t, t2} 156 157 So(tq.Add(c, "default", expect...), ShouldBeNil) 158 So(len(expect), ShouldEqual, 2) 159 So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 2) 160 161 names := []string{"16045561405319332057", "16045561405319332058"} 162 163 for i := range expect { 164 Convey(fmt.Sprintf("task %d: %s", i, expect[i].Path), func() { 165 So(expect[i].Method, ShouldEqual, "POST") 166 So(expect[i].ETA, ShouldHappenOnOrBefore, now) 167 So(expect[i].Name, ShouldEqual, names[i]) 168 }) 169 } 170 171 Convey("stats work too", func() { 172 delay := -time.Second * 400 173 174 t := &tq.Task{Path: "/somewhere"} 175 t.Delay = delay 176 So(tq.Add(c, "", t), ShouldBeNil) 177 178 stats, err := tq.Stats(c, "") 179 So(err, ShouldBeNil) 180 So(stats[0].Tasks, ShouldEqual, 3) 181 So(stats[0].OldestETA, ShouldHappenOnOrBefore, clock.Now(c).Add(delay)) 182 183 _, err = tq.Stats(c, "noexist") 184 So(err.Error(), ShouldContainSubstring, "UNKNOWN_QUEUE") 185 }) 186 187 Convey("can purge all tasks", func() { 188 So(tq.Add(c, "", &tq.Task{Path: "/wut/nerbs"}), ShouldBeNil) 189 So(tq.Purge(c, ""), ShouldBeNil) 190 191 So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 0) 192 So(len(tqt.GetTombstonedTasks()["default"]), ShouldEqual, 0) 193 So(len(tqt.GetTransactionTasks()["default"]), ShouldEqual, 0) 194 195 Convey("purging a queue which DNE fails", func() { 196 So(tq.Purge(c, "noexist").Error(), ShouldContainSubstring, "UNKNOWN_QUEUE") 197 }) 198 }) 199 200 }) 201 }) 202 203 Convey("Delete", func() { 204 t := &tq.Task{Path: "/hello/world"} 205 So(tq.Add(c, "", t), ShouldBeNil) 206 207 Convey("works", func() { 208 err := tq.Delete(c, "", t) 209 So(err, ShouldBeNil) 210 So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 0) 211 So(len(tqt.GetTombstonedTasks()["default"]), ShouldEqual, 1) 212 So(tqt.GetTombstonedTasks()["default"][t.Name], ShouldResemble, t) 213 }) 214 215 Convey("cannot delete a task twice", func() { 216 So(tq.Delete(c, "", t), ShouldBeNil) 217 218 So(tq.Delete(c, "", t).Error(), ShouldContainSubstring, "TOMBSTONED_TASK") 219 220 Convey("but you can if you do a reset", func() { 221 tqt.ResetTasks() 222 223 So(tq.Add(c, "", t), ShouldBeNil) 224 So(tq.Delete(c, "", t), ShouldBeNil) 225 }) 226 }) 227 228 Convey("cannot delete from bogus queues", func() { 229 err := tq.Delete(c, "wat", t) 230 So(err.Error(), ShouldContainSubstring, "UNKNOWN_QUEUE") 231 }) 232 233 Convey("cannot delete a missing task", func() { 234 t.Name = "tarntioarenstyw" 235 err := tq.Delete(c, "", t) 236 So(err.Error(), ShouldContainSubstring, "UNKNOWN_TASK") 237 }) 238 239 Convey("DeleteMulti also works", func() { 240 t2 := t.Duplicate() 241 t2.Name = "" 242 t2.Path = "/hi/city" 243 So(tq.Add(c, "", t2), ShouldBeNil) 244 245 Convey("usually works", func() { 246 So(tq.Delete(c, "", t, t2), ShouldBeNil) 247 So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 0) 248 So(len(tqt.GetTombstonedTasks()["default"]), ShouldEqual, 2) 249 }) 250 }) 251 }) 252 }) 253 254 Convey("works with transactions", func() { 255 t := &tq.Task{Path: "/hello/world"} 256 So(tq.Add(c, "", t), ShouldBeNil) 257 258 t2 := &tq.Task{Path: "/hi/city"} 259 So(tq.Add(c, "", t2), ShouldBeNil) 260 261 So(tq.Delete(c, "", t2), ShouldBeNil) 262 263 Convey("can view regular tasks", func() { 264 So(ds.RunInTransaction(c, func(c context.Context) error { 265 tqt := tq.Raw(c).GetTestable() 266 267 So(tqt.GetScheduledTasks()["default"][t.Name], ShouldResemble, t) 268 So(tqt.GetTombstonedTasks()["default"][t2.Name], ShouldResemble, t2) 269 So(tqt.GetTransactionTasks()["default"], ShouldBeNil) 270 return nil 271 }, nil), ShouldBeNil) 272 }) 273 274 Convey("can add a new task", func() { 275 t3 := &tq.Task{Path: "/sandwitch/victory"} 276 277 err := ds.RunInTransaction(c, func(c context.Context) error { 278 tqt := tq.GetTestable(c) 279 280 So(tq.Add(c, "", t3), ShouldBeNil) 281 So(t3.Name, ShouldEqual, "16045561405319332059") 282 283 So(tqt.GetScheduledTasks()["default"][t.Name], ShouldResemble, t) 284 So(tqt.GetTombstonedTasks()["default"][t2.Name], ShouldResemble, t2) 285 So(tqt.GetTransactionTasks()["default"][0], ShouldResemble, t3) 286 return nil 287 }, nil) 288 So(err, ShouldBeNil) 289 290 for _, tsk := range tqt.GetScheduledTasks()["default"] { 291 if tsk.Name == t.Name { 292 So(tsk, ShouldResemble, t) 293 } else { 294 So(tsk, ShouldResemble, t3) 295 } 296 } 297 298 So(tqt.GetTombstonedTasks()["default"][t2.Name], ShouldResemble, t2) 299 So(tqt.GetTransactionTasks()["default"], ShouldBeNil) 300 }) 301 302 Convey("can add a new task (but reset the state in a test)", func() { 303 t3 := &tq.Task{Path: "/sandwitch/victory"} 304 305 var txnCtx context.Context 306 So(ds.RunInTransaction(c, func(c context.Context) error { 307 txnCtx = c 308 tqt := tq.GetTestable(c) 309 310 So(tq.Add(c, "", t3), ShouldBeNil) 311 312 So(tqt.GetScheduledTasks()["default"][t.Name], ShouldResemble, t) 313 So(tqt.GetTombstonedTasks()["default"][t2.Name], ShouldResemble, t2) 314 So(tqt.GetTransactionTasks()["default"][0], ShouldResemble, t3) 315 316 tqt.ResetTasks() 317 318 So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 0) 319 So(len(tqt.GetTombstonedTasks()["default"]), ShouldEqual, 0) 320 So(len(tqt.GetTransactionTasks()["default"]), ShouldEqual, 0) 321 322 return nil 323 }, nil), ShouldBeNil) 324 325 So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 0) 326 So(len(tqt.GetTombstonedTasks()["default"]), ShouldEqual, 0) 327 So(len(tqt.GetTransactionTasks()["default"]), ShouldEqual, 0) 328 329 Convey("and reusing a closed context is bad times", func() { 330 So(tq.Add(txnCtx, "", nil).Error(), ShouldContainSubstring, "expired") 331 }) 332 }) 333 334 Convey("you can AddMulti as well", func() { 335 So(ds.RunInTransaction(c, func(c context.Context) error { 336 tqt := tq.GetTestable(c) 337 338 t.Name = "" 339 tasks := []*tq.Task{t.Duplicate(), t.Duplicate(), t.Duplicate()} 340 So(tq.Add(c, "", tasks...), ShouldBeNil) 341 So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 1) 342 So(len(tqt.GetTransactionTasks()["default"]), ShouldEqual, 3) 343 return nil 344 }, nil), ShouldBeNil) 345 So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 4) 346 So(len(tqt.GetTransactionTasks()["default"]), ShouldEqual, 0) 347 }) 348 349 Convey("unless you add too many things", func() { 350 t.Name = "" 351 352 So(ds.RunInTransaction(c, func(c context.Context) error { 353 for i := 0; i < 5; i++ { 354 So(tq.Add(c, "", t.Duplicate()), ShouldBeNil) 355 } 356 So(tq.Add(c, "", t).Error(), ShouldContainSubstring, "BAD_REQUEST") 357 return nil 358 }, nil), ShouldBeNil) 359 }) 360 361 Convey("unless you Add to a bad queue", func() { 362 t.Name = "" 363 364 So(ds.RunInTransaction(c, func(c context.Context) error { 365 So(tq.Add(c, "meat", t).Error(), ShouldContainSubstring, "UNKNOWN_QUEUE") 366 367 Convey("unless you add it!", func() { 368 tq.Raw(c).GetTestable().CreateQueue("meat") 369 So(tq.Add(c, "meat", t), ShouldBeNil) 370 }) 371 372 return nil 373 }, nil), ShouldBeNil) 374 }) 375 376 Convey("unless the task is named", func() { 377 So(ds.RunInTransaction(c, func(c context.Context) error { 378 err := tq.Add(c, "", t) // Note: "t" has a Name from initial Add. 379 So(err, ShouldNotBeNil) 380 So(err.Error(), ShouldContainSubstring, "INVALID_TASK_NAME") 381 382 return nil 383 }, nil), ShouldBeNil) 384 }) 385 386 Convey("No other features are available, however", func() { 387 So(ds.RunInTransaction(c, func(c context.Context) error { 388 So(tq.Delete(c, "", t).Error(), ShouldContainSubstring, "cannot DeleteMulti from a transaction") 389 So(tq.Purge(c, "").Error(), ShouldContainSubstring, "cannot Purge from a transaction") 390 _, err := tq.Stats(c, "") 391 So(err.Error(), ShouldContainSubstring, "cannot Stats from a transaction") 392 return nil 393 }, nil), ShouldBeNil) 394 }) 395 396 Convey("can get the non-transactional taskqueue context though", func() { 397 So(ds.RunInTransaction(c, func(c context.Context) error { 398 noTxn := ds.WithoutTransaction(c) 399 So(tq.Delete(noTxn, "", t), ShouldBeNil) 400 So(tq.Purge(noTxn, ""), ShouldBeNil) 401 _, err := tq.Stats(noTxn, "") 402 So(err, ShouldBeNil) 403 return nil 404 }, nil), ShouldBeNil) 405 }) 406 407 Convey("adding a new task only happens if we don't errout", func() { 408 So(ds.RunInTransaction(c, func(c context.Context) error { 409 t3 := &tq.Task{Path: "/sandwitch/victory"} 410 So(tq.Add(c, "", t3), ShouldBeNil) 411 return fmt.Errorf("nooooo") 412 }, nil), ShouldErrLike, "nooooo") 413 414 So(tqt.GetScheduledTasks()["default"][t.Name], ShouldResemble, t) 415 So(tqt.GetTombstonedTasks()["default"][t2.Name], ShouldResemble, t2) 416 So(tqt.GetTransactionTasks()["default"], ShouldBeNil) 417 }) 418 419 Convey("likewise, a panic doesn't schedule anything", func() { 420 func() { 421 defer func() { _ = recover() }() 422 So(ds.RunInTransaction(c, func(c context.Context) error { 423 So(tq.Add(c, "", &tq.Task{Path: "/sandwitch/victory"}), ShouldBeNil) 424 425 panic(fmt.Errorf("nooooo")) 426 }, nil), ShouldBeNil) 427 }() 428 429 So(tqt.GetScheduledTasks()["default"][t.Name], ShouldResemble, t) 430 So(tqt.GetTombstonedTasks()["default"][t2.Name], ShouldResemble, t2) 431 So(tqt.GetTransactionTasks()["default"], ShouldBeNil) 432 }) 433 434 }) 435 436 Convey("Pull queues", func() { 437 tqt.CreatePullQueue("pull") 438 tqt.CreateQueue("push") 439 440 Convey("One task scenarios", func() { 441 Convey("enqueue, lease, delete", func() { 442 // Enqueue. 443 err := tq.Add(c, "pull", &tq.Task{ 444 Method: "PULL", 445 Payload: []byte("zzz"), 446 Tag: "tag", 447 }) 448 So(err, ShouldBeNil) 449 450 // Lease. 451 tasks, err := tq.Lease(c, 1, "pull", time.Minute) 452 So(err, ShouldBeNil) 453 So(len(tasks), ShouldEqual, 1) 454 So(tasks[0].Payload, ShouldResemble, []byte("zzz")) 455 456 // "Disappears" from the queue while leased. 457 tc.Add(30 * time.Second) 458 tasks2, err := tq.Lease(c, 1, "pull", time.Minute) 459 So(err, ShouldBeNil) 460 So(len(tasks2), ShouldEqual, 0) 461 462 // Remove after "processing". 463 So(tq.Delete(c, "pull", tasks[0]), ShouldBeNil) 464 465 // Still nothing there even after lease expires. 466 tc.Add(50 * time.Second) 467 tasks3, err := tq.Lease(c, 1, "pull", time.Minute) 468 So(err, ShouldBeNil) 469 So(len(tasks3), ShouldEqual, 0) 470 }) 471 472 Convey("enqueue, lease, loose", func() { 473 // Enqueue. 474 err := tq.Add(c, "pull", &tq.Task{ 475 Method: "PULL", 476 Payload: []byte("zzz"), 477 }) 478 So(err, ShouldBeNil) 479 480 // Lease. 481 tasks, err := tq.Lease(c, 1, "pull", time.Minute) 482 So(err, ShouldBeNil) 483 So(len(tasks), ShouldEqual, 1) 484 So(tasks[0].Payload, ShouldResemble, []byte("zzz")) 485 486 // Time passes, lease expires. 487 tc.Add(61 * time.Second) 488 489 // Available again, someone grabs it. 490 tasks2, err := tq.Lease(c, 1, "pull", time.Minute) 491 So(err, ShouldBeNil) 492 So(len(tasks2), ShouldEqual, 1) 493 So(tasks2[0].Payload, ShouldResemble, []byte("zzz")) 494 495 // Previously leased task is no longer owned. 496 err = tq.ModifyLease(c, tasks[0], "pull", time.Minute) 497 So(err, ShouldErrLike, "TASK_LEASE_EXPIRED") 498 }) 499 500 Convey("enqueue, lease, sleep", func() { 501 // Enqueue. 502 err := tq.Add(c, "pull", &tq.Task{ 503 Method: "PULL", 504 Payload: []byte("zzz"), 505 }) 506 So(err, ShouldBeNil) 507 508 // Lease. 509 tasks, err := tq.Lease(c, 1, "pull", time.Minute) 510 So(err, ShouldBeNil) 511 So(len(tasks), ShouldEqual, 1) 512 So(tasks[0].Payload, ShouldResemble, []byte("zzz")) 513 514 // Time passes, the lease expires. 515 tc.Add(61 * time.Second) 516 err = tq.ModifyLease(c, tasks[0], "pull", time.Minute) 517 So(err, ShouldErrLike, "TASK_LEASE_EXPIRED") 518 }) 519 520 Convey("enqueue, lease, extend", func() { 521 // Enqueue. 522 err := tq.Add(c, "pull", &tq.Task{ 523 Method: "PULL", 524 Payload: []byte("zzz"), 525 }) 526 So(err, ShouldBeNil) 527 528 // Lease. 529 tasks, err := tq.Lease(c, 1, "pull", time.Minute) 530 So(err, ShouldBeNil) 531 So(len(tasks), ShouldEqual, 1) 532 So(tasks[0].Payload, ShouldResemble, []byte("zzz")) 533 534 // Time passes, the lease is updated. 535 tc.Add(59 * time.Second) 536 err = tq.ModifyLease(c, tasks[0], "pull", time.Minute) 537 So(err, ShouldBeNil) 538 539 // Not available, still leased. 540 tc.Add(30 * time.Second) 541 tasks2, err := tq.Lease(c, 1, "pull", time.Minute) 542 So(err, ShouldBeNil) 543 So(len(tasks2), ShouldEqual, 0) 544 }) 545 546 Convey("enqueue, lease, return", func() { 547 // Enqueue. 548 err := tq.Add(c, "pull", &tq.Task{ 549 Method: "PULL", 550 Payload: []byte("zzz"), 551 }) 552 So(err, ShouldBeNil) 553 554 // Lease. 555 tasks, err := tq.Lease(c, 1, "pull", time.Minute) 556 So(err, ShouldBeNil) 557 So(len(tasks), ShouldEqual, 1) 558 So(tasks[0].Payload, ShouldResemble, []byte("zzz")) 559 560 // Put back by using 0 sec lease. 561 err = tq.ModifyLease(c, tasks[0], "pull", 0) 562 So(err, ShouldBeNil) 563 564 // Available again. 565 tasks2, err := tq.Lease(c, 1, "pull", time.Minute) 566 So(err, ShouldBeNil) 567 So(len(tasks2), ShouldEqual, 1) 568 }) 569 570 Convey("lease by existing tag", func() { 571 // Enqueue. 572 err := tq.Add(c, "pull", &tq.Task{ 573 Method: "PULL", 574 Payload: []byte("zzz"), 575 Tag: "tag", 576 }) 577 So(err, ShouldBeNil) 578 579 // Try different tag first, should return nothing. 580 tasks, err := tq.LeaseByTag(c, 1, "pull", time.Minute, "wrong_tag") 581 So(err, ShouldBeNil) 582 So(len(tasks), ShouldEqual, 0) 583 584 // Leased. 585 tasks, err = tq.LeaseByTag(c, 1, "pull", time.Minute, "tag") 586 So(err, ShouldBeNil) 587 So(len(tasks), ShouldEqual, 1) 588 589 // No ready tasks anymore. 590 tasks2, err := tq.LeaseByTag(c, 1, "pull", time.Minute, "tag") 591 So(err, ShouldBeNil) 592 So(len(tasks2), ShouldEqual, 0) 593 tasks2, err = tq.Lease(c, 1, "pull", time.Minute) 594 So(err, ShouldBeNil) 595 So(len(tasks2), ShouldEqual, 0) 596 597 // Return back to the queue later. 598 tc.Add(30 * time.Second) 599 So(tq.ModifyLease(c, tasks[0], "pull", 0), ShouldBeNil) 600 601 // Available again. 602 tasks, err = tq.LeaseByTag(c, 1, "pull", time.Minute, "tag") 603 So(err, ShouldBeNil) 604 So(len(tasks), ShouldEqual, 1) 605 }) 606 607 Convey("transactions (success)", func() { 608 So(ds.RunInTransaction(c, func(c context.Context) error { 609 return tq.Add(c, "pull", &tq.Task{ 610 Method: "PULL", 611 Payload: []byte("zzz"), 612 }) 613 }, nil), ShouldBeNil) 614 615 tasks, err := tq.Lease(c, 1, "pull", time.Minute) 616 So(err, ShouldBeNil) 617 So(len(tasks), ShouldEqual, 1) 618 }) 619 620 Convey("transactions (rollback)", func() { 621 So(ds.RunInTransaction(c, func(c context.Context) error { 622 err := tq.Add(c, "pull", &tq.Task{ 623 Method: "PULL", 624 Payload: []byte("zzz"), 625 }) 626 So(err, ShouldBeNil) 627 return fmt.Errorf("meh") 628 }, nil), ShouldErrLike, "meh") 629 630 tasks, err := tq.Lease(c, 1, "pull", time.Minute) 631 So(err, ShouldBeNil) 632 So(len(tasks), ShouldEqual, 0) 633 }) 634 635 Convey("transactions (invalid ops)", func() { 636 So(ds.RunInTransaction(c, func(c context.Context) error { 637 _, err := tq.Lease(c, 1, "pull", time.Minute) 638 So(err, ShouldErrLike, "cannot Lease") 639 640 _, err = tq.LeaseByTag(c, 1, "pull", time.Minute, "tag") 641 So(err, ShouldErrLike, "cannot LeaseByTag") 642 643 err = tq.ModifyLease(c, &tq.Task{}, "pull", time.Minute) 644 So(err, ShouldErrLike, "cannot ModifyLease") 645 646 return nil 647 }, nil), ShouldBeNil) 648 }) 649 650 Convey("wrong queue mode", func() { 651 err := tq.Add(c, "pull", &tq.Task{ 652 Method: "POST", 653 Payload: []byte("zzz"), 654 }) 655 So(err, ShouldErrLike, "INVALID_QUEUE_MODE") 656 657 err = tq.Add(c, "push", &tq.Task{ 658 Method: "PULL", 659 Payload: []byte("zzz"), 660 }) 661 So(err, ShouldErrLike, "INVALID_QUEUE_MODE") 662 663 _, err = tq.Lease(c, 1, "push", time.Minute) 664 So(err, ShouldErrLike, "INVALID_QUEUE_MODE") 665 666 err = tq.ModifyLease(c, &tq.Task{}, "push", time.Minute) 667 So(err, ShouldErrLike, "INVALID_QUEUE_MODE") 668 }) 669 670 Convey("bad requests", func() { 671 _, err := tq.Lease(c, 0, "pull", time.Minute) 672 So(err, ShouldErrLike, "BAD_REQUEST") 673 674 _, err = tq.Lease(c, 1, "pull", -time.Minute) 675 So(err, ShouldErrLike, "BAD_REQUEST") 676 677 err = tq.ModifyLease(c, &tq.Task{}, "pull", -time.Minute) 678 So(err, ShouldErrLike, "BAD_REQUEST") 679 }) 680 681 Convey("tombstoned task", func() { 682 task := &tq.Task{ 683 Method: "PULL", 684 Name: "deleted", 685 Payload: []byte("zzz"), 686 } 687 So(tq.Add(c, "pull", task), ShouldBeNil) 688 So(tq.Delete(c, "pull", task), ShouldBeNil) 689 690 err := tq.ModifyLease(c, task, "pull", time.Minute) 691 So(err, ShouldErrLike, "TOMBSTONED_TASK") 692 }) 693 694 Convey("missing task", func() { 695 err := tq.ModifyLease(c, &tq.Task{Name: "missing"}, "pull", time.Minute) 696 So(err, ShouldErrLike, "UNKNOWN_TASK") 697 }) 698 }) 699 700 Convey("Many-tasks scenarios (sorting)", func() { 701 Convey("Lease sorts by ETA (no tags)", func() { 702 now := clock.Now(c) 703 704 tasks := []*tq.Task{} 705 for i := 0; i < 5; i++ { 706 tasks = append(tasks, &tq.Task{ 707 Method: "PULL", 708 Name: fmt.Sprintf("task-%d", i), 709 ETA: now.Add(time.Duration(i+1) * time.Second), 710 }) 711 } 712 713 // Add in some "random" order. 714 err := tq.Add(c, "pull", tasks[4], tasks[2], tasks[0], tasks[1], tasks[3]) 715 So(err, ShouldBeNil) 716 717 // Nothing to pull, no available tasks yet. 718 leased, err := tq.Lease(c, 1, "pull", time.Minute) 719 So(err, ShouldBeNil) 720 So(len(leased), ShouldEqual, 0) 721 722 tc.Add(time.Second) 723 724 // First task appears. 725 leased, err = tq.Lease(c, 1, "pull", time.Minute) 726 So(err, ShouldBeNil) 727 So(len(leased), ShouldEqual, 1) 728 So(leased[0].Name, ShouldEqual, "task-0") 729 730 tc.Add(4 * time.Second) 731 732 // The rest of them appear, in sorted order. 733 leased, err = tq.Lease(c, 100, "pull", time.Minute) 734 So(err, ShouldBeNil) 735 So(len(leased), ShouldEqual, 4) 736 for i := 0; i < 4; i++ { 737 So(leased[i].Name, ShouldEqual, fmt.Sprintf("task-%d", i+1)) 738 } 739 }) 740 }) 741 742 Convey("Lease and forget (no tags)", func() { 743 now := clock.Now(c) 744 745 for i := 0; i < 5; i++ { 746 err := tq.Add(c, "pull", &tq.Task{ 747 Method: "PULL", 748 Name: fmt.Sprintf("task-%d", i), 749 ETA: now.Add(time.Duration(i+1) * time.Second), 750 }) 751 So(err, ShouldBeNil) 752 } 753 754 tc.Add(time.Second) 755 756 // Lease the first task for 3 sec. 757 leased, err := tq.Lease(c, 1, "pull", 3*time.Second) 758 So(err, ShouldBeNil) 759 So(len(leased), ShouldEqual, 1) 760 So(leased[0].Name, ShouldEqual, "task-0") 761 762 // "Forget" about the lease. 763 tc.Add(10 * time.Second) 764 765 // Lease all we have there. 766 leased, err = tq.Lease(c, 100, "pull", time.Minute) 767 So(err, ShouldBeNil) 768 So(len(leased), ShouldEqual, 5) 769 So(leased[0].Name, ShouldEqual, "task-1") 770 So(leased[1].Name, ShouldEqual, "task-2") 771 So(leased[2].Name, ShouldEqual, "task-0") 772 So(leased[3].Name, ShouldEqual, "task-3") 773 So(leased[4].Name, ShouldEqual, "task-4") 774 }) 775 776 Convey("Modify lease moves the task (no tags)", func() { 777 now := clock.Now(c) 778 779 for i := 0; i < 5; i++ { 780 err := tq.Add(c, "pull", &tq.Task{ 781 Method: "PULL", 782 Name: fmt.Sprintf("task-%d", i), 783 ETA: now.Add(time.Duration(i+1) * time.Second), 784 }) 785 So(err, ShouldBeNil) 786 } 787 788 tc.Add(time.Second) 789 790 // Lease the first task for 1 minute. 791 leased, err := tq.Lease(c, 1, "pull", time.Minute) 792 So(err, ShouldBeNil) 793 So(len(leased), ShouldEqual, 1) 794 So(leased[0].Name, ShouldEqual, "task-0") 795 796 // 3 sec later release the lease. 797 tc.Add(3 * time.Second) 798 So(tq.ModifyLease(c, leased[0], "pull", 0), ShouldEqual, nil) 799 800 tc.Add(10 * time.Second) 801 802 // Lease all we have there. 803 leased, err = tq.Lease(c, 100, "pull", time.Minute) 804 So(err, ShouldBeNil) 805 So(len(leased), ShouldEqual, 5) 806 So(leased[0].Name, ShouldEqual, "task-1") 807 So(leased[1].Name, ShouldEqual, "task-2") 808 So(leased[2].Name, ShouldEqual, "task-0") 809 So(leased[3].Name, ShouldEqual, "task-3") 810 So(leased[4].Name, ShouldEqual, "task-4") 811 }) 812 813 Convey("Delete task deletes from the middle", func() { 814 now := clock.Now(c) 815 816 for i := 0; i < 5; i++ { 817 err := tq.Add(c, "pull", &tq.Task{ 818 Method: "PULL", 819 Name: fmt.Sprintf("task-%d", i), 820 ETA: now.Add(time.Duration(i+1) * time.Second), 821 }) 822 So(err, ShouldBeNil) 823 } 824 825 tc.Add(time.Second) 826 827 // Lease the first task for 3 sec. 828 leased, err := tq.Lease(c, 1, "pull", 3*time.Second) 829 So(err, ShouldBeNil) 830 So(len(leased), ShouldEqual, 1) 831 So(leased[0].Name, ShouldEqual, "task-0") 832 833 // Kill it. 834 So(tq.Delete(c, "pull", leased[0]), ShouldBeNil) 835 836 tc.Add(10 * time.Second) 837 838 // Lease all we have there. 839 leased, err = tq.Lease(c, 100, "pull", time.Minute) 840 So(err, ShouldBeNil) 841 So(len(leased), ShouldEqual, 4) 842 So(leased[0].Name, ShouldEqual, "task-1") 843 So(leased[1].Name, ShouldEqual, "task-2") 844 So(leased[2].Name, ShouldEqual, "task-3") 845 So(leased[3].Name, ShouldEqual, "task-4") 846 }) 847 848 Convey("Tags work", func() { 849 now := clock.Now(c) 850 851 for i := 0; i < 5; i++ { 852 err := tq.Add(c, "pull", 853 &tq.Task{ 854 Method: "PULL", 855 Name: fmt.Sprintf("task-%d", i), 856 ETA: now.Add(time.Duration(i+1) * time.Second), 857 }, 858 &tq.Task{ 859 Method: "PULL", 860 Name: fmt.Sprintf("task-a-%d", i), 861 ETA: now.Add(time.Duration(i+1) * time.Second), 862 Tag: "a", 863 }, 864 &tq.Task{ 865 Method: "PULL", 866 Name: fmt.Sprintf("task-b-%d", i), 867 ETA: now.Add(time.Duration(i+1) * time.Second), 868 Tag: "b", 869 }) 870 So(err, ShouldBeNil) 871 } 872 873 tc.Add(time.Second) 874 875 // Lease leases all regardless of tags. 876 leased, err := tq.Lease(c, 100, "pull", time.Minute) 877 So(err, ShouldBeNil) 878 So(len(leased), ShouldEqual, 3) 879 So(leased[0].Name, ShouldEqual, "task-0") 880 So(leased[1].Name, ShouldEqual, "task-a-0") 881 So(leased[2].Name, ShouldEqual, "task-b-0") 882 883 // Nothing to least per tag for now. 884 leased, err = tq.LeaseByTag(c, 100, "pull", time.Minute, "a") 885 So(err, ShouldBeNil) 886 So(len(leased), ShouldEqual, 0) 887 888 tc.Add(10 * time.Second) 889 890 // Grab all "a" tasks. 891 leased, err = tq.LeaseByTag(c, 100, "pull", time.Minute, "a") 892 So(err, ShouldBeNil) 893 So(len(leased), ShouldEqual, 4) 894 for i := 0; i < 4; i++ { 895 So(leased[i].Name, ShouldEqual, fmt.Sprintf("task-a-%d", i+1)) 896 } 897 898 // Only "b" and untagged tasks left. 899 leased, err = tq.Lease(c, 100, "pull", time.Minute) 900 So(err, ShouldBeNil) 901 So(len(leased), ShouldEqual, 8) 902 for i := 0; i < 4; i++ { 903 So(leased[i*2].Name, ShouldEqual, fmt.Sprintf("task-%d", i+1)) 904 So(leased[i*2+1].Name, ShouldEqual, fmt.Sprintf("task-b-%d", i+1)) 905 } 906 }) 907 908 Convey("LeaseByTag with empty tag, hitting tag first", func() { 909 now := clock.Now(c) 910 911 // Nothing to pull, nothing there yet. 912 leased, err := tq.LeaseByTag(c, 1, "pull", time.Minute, "") 913 So(err, ShouldBeNil) 914 So(len(leased), ShouldEqual, 0) 915 916 for i := 0; i < 5; i++ { 917 err := tq.Add(c, "pull", 918 &tq.Task{ 919 Method: "PULL", 920 Name: fmt.Sprintf("task-a-%d", i), 921 ETA: now.Add(time.Duration(i*2+1) * time.Second), 922 Tag: "a", 923 }, 924 &tq.Task{ 925 Method: "PULL", 926 Name: fmt.Sprintf("task-%d", i), 927 ETA: now.Add(time.Duration(i*2+2) * time.Second), 928 }) 929 So(err, ShouldBeNil) 930 } 931 932 // Nothing to pull, no available tasks yet. 933 leased, err = tq.LeaseByTag(c, 1, "pull", time.Minute, "") 934 So(err, ShouldBeNil) 935 So(len(leased), ShouldEqual, 0) 936 937 tc.Add(time.Minute) 938 939 // Hits "a" first and fetches only "a" tasks. 940 leased, err = tq.LeaseByTag(c, 100, "pull", time.Minute, "") 941 So(err, ShouldBeNil) 942 So(len(leased), ShouldEqual, 5) 943 for i := 0; i < 5; i++ { 944 So(leased[i].Name, ShouldEqual, fmt.Sprintf("task-a-%d", i)) 945 } 946 }) 947 948 Convey("LeaseByTag with empty tag, hitting untagged first", func() { 949 now := clock.Now(c) 950 951 for i := 0; i < 5; i++ { 952 err := tq.Add(c, "pull", 953 &tq.Task{ 954 Method: "PULL", 955 Name: fmt.Sprintf("task-%d", i), 956 ETA: now.Add(time.Duration(i*2+1) * time.Second), 957 }, 958 &tq.Task{ 959 Method: "PULL", 960 Name: fmt.Sprintf("task-a-%d", i), 961 ETA: now.Add(time.Duration(i*2+2) * time.Second), 962 Tag: "a", 963 }) 964 So(err, ShouldBeNil) 965 } 966 967 tc.Add(time.Minute) 968 969 // Hits "" first and fetches only "" tasks. 970 leased, err := tq.LeaseByTag(c, 100, "pull", time.Minute, "") 971 So(err, ShouldBeNil) 972 So(len(leased), ShouldEqual, 5) 973 for i := 0; i < 5; i++ { 974 So(leased[i].Name, ShouldEqual, fmt.Sprintf("task-%d", i)) 975 } 976 }) 977 }) 978 }) 979 }