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  }