OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package memory | |
6 | |
7 import ( | |
8 "fmt" | |
9 "infra/gae/libs/wrapper" | |
10 "infra/libs/clock" | |
11 "infra/libs/clock/testclock" | |
12 "math/rand" | |
13 "net/http" | |
14 "testing" | |
15 "time" | |
16 | |
17 . "github.com/smartystreets/goconvey/convey" | |
18 "golang.org/x/net/context" | |
19 | |
20 "appengine/taskqueue" | |
21 ) | |
22 | |
23 func TestTaskQueue(t *testing.T) { | |
24 t.Parallel() | |
25 | |
26 Convey("TaskQueue", t, func() { | |
27 now := time.Date(2000, time.January, 1, 1, 1, 1, 1, time.UTC) | |
28 c, tc := testclock.UseTime(context.Background(), now) | |
29 c = wrapper.SetMathRand(c, rand.New(rand.NewSource(clock.Now(c).
UnixNano()))) | |
30 c = Use(c) | |
31 | |
32 tq := wrapper.GetTQ(c).(interface { | |
33 wrapper.TQMultiReadWriter | |
34 wrapper.TQTestable | |
35 }) | |
36 | |
37 So(tq, ShouldNotBeNil) | |
38 | |
39 Convey("implements TQMultiReadWriter", func() { | |
40 Convey("Add", func() { | |
41 t := &taskqueue.Task{Path: "/hello/world"} | |
42 | |
43 Convey("works", func() { | |
44 t.Delay = 4 * time.Second | |
45 t.Header = http.Header{} | |
46 t.Header.Add("Cat", "tabby") | |
47 t.Payload = []byte("watwatwat") | |
48 t.RetryOptions = &taskqueue.RetryOptions
{AgeLimit: 7 * time.Second} | |
49 _, err := tq.Add(t, "") | |
50 So(err, ShouldBeNil) | |
51 name := "Z_UjshxM9ecyMQfGbZmUGOEcgxWU0_5
CGLl_-RntudwAw2DqQ5-58bzJiWQN4OKzeuUb9O4JrPkUw2rOvk2Ax46THojnQ6avBQgZdrKcJmrwQ6o
4qKfJdiyUbGXvy691yRfzLeQhs6cBhWrgf3wH-VPMcA4SC-zlbJ2U8An7I0zJQA5nBFnMNoMgT-2peGo
ay3rCSbj4z9VFFm9kS_i6JCaQH518ujLDSNCYdjTq6B6lcWrZAh0U_q3a1S2nXEwrKiw_t9MTNQFgAQZ
WyGBbvZQPmeRYtu8SPaWzTfd25v_YWgBuVL2rRSPSMvlDwE04nNdtvVzE8vNNiA1zRimmdzKeqATQF9_
ReUvj4D7U8dcS703DZWfKMBLgBffY9jqCassOOOw77V72Oq5EVauUw3Qw0L6bBsfM9FtahTKUdabzRZj
XUoze3EK4KXPt3-wdidau-8JrVf2XFocjjZbwHoxcGvbtT3b4nGLDlgwdC00bwaFBZWff" | |
52 So(*tq.GetScheduledTasks()["default"][na
me], ShouldResemble, taskqueue.Task{ | |
53 ETA: now.Add(4 * time.S
econd), | |
54 Header: http.Header{"Cat":
[]string{"tabby"}}, | |
55 Method: "POST", | |
56 Name: name, | |
57 Path: "/hello/world", | |
58 Payload: []byte("watwatwat"
), | |
59 RetryOptions: &taskqueue.RetryOp
tions{AgeLimit: 7 * time.Second}, | |
60 }) | |
61 }) | |
62 | |
63 Convey("cannot add to bad queues", func() { | |
64 _, err := tq.Add(nil, "waaat") | |
65 So(err.Error(), ShouldContainSubstring,
"UNKNOWN_QUEUE") | |
66 | |
67 Convey("but you can add Queues when test
ing", func() { | |
68 tq.CreateQueue("waaat") | |
69 _, err := tq.Add(t, "waaat") | |
70 So(err, ShouldBeNil) | |
71 | |
72 Convey("you just can't add them
twice", func() { | |
73 So(func() { tq.CreateQue
ue("waaat") }, ShouldPanic) | |
74 }) | |
75 }) | |
76 }) | |
77 | |
78 Convey("requires a URL", func() { | |
79 t.Path = "" | |
80 tr, err := tq.Add(t, "") | |
81 So(err.Error(), ShouldContainSubstring,
"INVALID_URL") | |
82 So(tr, ShouldBeNil) | |
83 }) | |
84 | |
85 Convey("cannot add twice", func() { | |
86 t.Name = "bob" | |
87 _, err := tq.Add(t, "") | |
88 So(err, ShouldBeNil) | |
89 | |
90 // can't add the same one twice! | |
91 _, err = tq.Add(t, "") | |
92 So(err, ShouldEqual, taskqueue.ErrTaskAl
readyAdded) | |
93 }) | |
94 | |
95 Convey("cannot add deleted task", func() { | |
96 t.Name = "bob" | |
97 _, err := tq.Add(t, "") | |
98 So(err, ShouldBeNil) | |
99 | |
100 err = tq.Delete(t, "") | |
101 So(err, ShouldBeNil) | |
102 | |
103 // can't add a deleted task! | |
104 _, err = tq.Add(t, "") | |
105 So(err, ShouldEqual, taskqueue.ErrTaskAl
readyAdded) | |
106 }) | |
107 | |
108 Convey("cannot set ETA+Delay", func() { | |
109 t.ETA = clock.Now(c).Add(time.Hour) | |
110 tc.Add(time.Second) | |
111 t.Delay = time.Hour | |
112 So(func() { tq.Add(t, "") }, ShouldPanic
) | |
113 }) | |
114 | |
115 Convey("must use a reasonable method", func() { | |
116 t.Method = "Crystal" | |
117 _, err := tq.Add(t, "") | |
118 So(err.Error(), ShouldContainSubstring,
"bad method") | |
119 }) | |
120 | |
121 Convey("payload gets dumped for non POST/PUT met
hods", func() { | |
122 t.Method = "HEAD" | |
123 t.Payload = []byte("coool") | |
124 tq, err := tq.Add(t, "") | |
125 So(err, ShouldBeNil) | |
126 So(tq.Payload, ShouldBeNil) | |
127 | |
128 // check that it didn't modify our origi
nal | |
129 So(t.Payload, ShouldResemble, []byte("co
ool")) | |
130 }) | |
131 | |
132 Convey("invalid names are rejected", func() { | |
133 t.Name = "happy times" | |
134 _, err := tq.Add(t, "") | |
135 So(err.Error(), ShouldContainSubstring,
"INVALID_TASK_NAME") | |
136 }) | |
137 | |
138 Convey("can be broken", func() { | |
139 tq.BreakFeatures(nil, "Add") | |
140 _, err := tq.Add(t, "") | |
141 So(err.Error(), ShouldContainSubstring,
"TRANSIENT_ERROR") | |
142 }) | |
143 | |
144 Convey("AddMulti also works", func() { | |
145 t2 := dupTask(t) | |
146 t2.Path = "/hi/city" | |
147 | |
148 expect := []*taskqueue.Task{t, t2} | |
149 | |
150 tasks, err := tq.AddMulti(expect, "defau
lt") | |
151 So(err, ShouldBeNil) | |
152 So(len(tasks), ShouldEqual, 2) | |
153 So(len(tq.GetScheduledTasks()["default"]
), ShouldEqual, 2) | |
154 | |
155 for i := range expect { | |
156 Convey(fmt.Sprintf("task %d: %s"
, i, expect[i].Path), func() { | |
157 expect[i].Method = "POST
" | |
158 expect[i].ETA = now | |
159 So(expect[i].Name, Shoul
dEqual, "") | |
160 So(len(tasks[i].Name), S
houldEqual, 500) | |
161 tasks[i].Name = "" | |
162 So(tasks[i], ShouldResem
ble, expect[i]) | |
163 }) | |
164 } | |
165 | |
166 Convey("can be broken", func() { | |
167 tq.BreakFeatures(nil, "AddMulti"
) | |
168 _, err := tq.AddMulti([]*taskque
ue.Task{t}, "") | |
169 So(err.Error(), ShouldContainSub
string, "TRANSIENT_ERROR") | |
170 }) | |
171 | |
172 Convey("is not broken by Add", func() { | |
173 tq.BreakFeatures(nil, "Add") | |
174 _, err := tq.AddMulti([]*taskque
ue.Task{t}, "") | |
175 So(err, ShouldBeNil) | |
176 }) | |
177 }) | |
178 }) | |
179 | |
180 Convey("Delete", func() { | |
181 t := &taskqueue.Task{Path: "/hello/world"} | |
182 tEnQ, err := tq.Add(t, "") | |
183 So(err, ShouldBeNil) | |
184 | |
185 Convey("works", func() { | |
186 t.Name = tEnQ.Name | |
187 err := tq.Delete(t, "") | |
188 So(err, ShouldBeNil) | |
189 So(len(tq.GetScheduledTasks()["default"]
), ShouldEqual, 0) | |
190 So(len(tq.GetTombstonedTasks()["default"
]), ShouldEqual, 1) | |
191 So(tq.GetTombstonedTasks()["default"][tE
nQ.Name], ShouldResemble, tEnQ) | |
192 }) | |
193 | |
194 Convey("cannot delete a task twice", func() { | |
195 err := tq.Delete(tEnQ, "") | |
196 So(err, ShouldBeNil) | |
197 | |
198 err = tq.Delete(tEnQ, "") | |
199 So(err.Error(), ShouldContainSubstring,
"TOMBSTONED_TASK") | |
200 | |
201 Convey("but you can if you do a reset",
func() { | |
202 tq.ResetTasks() | |
203 | |
204 tEnQ, err := tq.Add(t, "") | |
205 So(err, ShouldBeNil) | |
206 err = tq.Delete(tEnQ, "") | |
207 So(err, ShouldBeNil) | |
208 }) | |
209 }) | |
210 | |
211 Convey("cannot delete from bogus queues", func()
{ | |
212 err := tq.Delete(t, "wat") | |
213 So(err.Error(), ShouldContainSubstring,
"UNKNOWN_QUEUE") | |
214 }) | |
215 | |
216 Convey("cannot delete a missing task", func() { | |
217 t.Name = "tarntioarenstyw" | |
218 err := tq.Delete(t, "") | |
219 So(err.Error(), ShouldContainSubstring,
"UNKNOWN_TASK") | |
220 }) | |
221 | |
222 Convey("can be broken", func() { | |
223 tq.BreakFeatures(nil, "Delete") | |
224 err := tq.Delete(t, "") | |
225 So(err.Error(), ShouldContainSubstring,
"TRANSIENT_ERROR") | |
226 }) | |
227 | |
228 Convey("DeleteMulti also works", func() { | |
229 t2 := dupTask(t) | |
230 t2.Path = "/hi/city" | |
231 tEnQ2, err := tq.Add(t2, "") | |
232 So(err, ShouldBeNil) | |
233 | |
234 Convey("usually works", func() { | |
235 err = tq.DeleteMulti([]*taskqueu
e.Task{tEnQ, tEnQ2}, "") | |
236 So(err, ShouldBeNil) | |
237 So(len(tq.GetScheduledTasks()["d
efault"]), ShouldEqual, 0) | |
238 So(len(tq.GetTombstonedTasks()["
default"]), ShouldEqual, 2) | |
239 }) | |
240 | |
241 Convey("can be broken", func() { | |
242 tq.BreakFeatures(nil, "DeleteMul
ti") | |
243 err = tq.DeleteMulti([]*taskqueu
e.Task{tEnQ, tEnQ2}, "") | |
244 So(err.Error(), ShouldContainSub
string, "TRANSIENT_ERROR") | |
245 }) | |
246 | |
247 Convey("is not broken by Delete", func()
{ | |
248 tq.BreakFeatures(nil, "Delete") | |
249 err = tq.DeleteMulti([]*taskqueu
e.Task{tEnQ, tEnQ2}, "") | |
250 So(err, ShouldBeNil) | |
251 }) | |
252 }) | |
253 }) | |
254 }) | |
255 | |
256 Convey("works with transactions", func() { | |
257 t := &taskqueue.Task{Path: "/hello/world"} | |
258 tEnQ, err := tq.Add(t, "") | |
259 So(err, ShouldBeNil) | |
260 | |
261 t2 := &taskqueue.Task{Path: "/hi/city"} | |
262 tEnQ2, err := tq.Add(t2, "") | |
263 So(err, ShouldBeNil) | |
264 | |
265 err = tq.Delete(tEnQ2, "") | |
266 So(err, ShouldBeNil) | |
267 | |
268 Convey("can view regular tasks", func() { | |
269 wrapper.GetDS(c).RunInTransaction(func(c context
.Context) error { | |
270 tq := wrapper.GetTQ(c).(interface { | |
271 wrapper.TQTestable | |
272 wrapper.TaskQueue | |
273 }) | |
274 | |
275 So(tq.GetScheduledTasks()["default"][tEn
Q.Name], ShouldResemble, tEnQ) | |
276 So(tq.GetTombstonedTasks()["default"][tE
nQ2.Name], ShouldResemble, tEnQ2) | |
277 So(tq.GetTransactionTasks()["default"],
ShouldBeNil) | |
278 return nil | |
279 }, nil) | |
280 }) | |
281 | |
282 Convey("can add a new task", func() { | |
283 tEnQ3 := (*taskqueue.Task)(nil) | |
284 | |
285 wrapper.GetDS(c).RunInTransaction(func(c context
.Context) error { | |
286 tq := wrapper.GetTQ(c).(interface { | |
287 wrapper.TQTestable | |
288 wrapper.TaskQueue | |
289 }) | |
290 | |
291 t3 := &taskqueue.Task{Path: "/sandwitch/
victory"} | |
292 tEnQ3, err = tq.Add(t3, "") | |
293 So(err, ShouldBeNil) | |
294 | |
295 So(tq.GetScheduledTasks()["default"][tEn
Q.Name], ShouldResemble, tEnQ) | |
296 So(tq.GetTombstonedTasks()["default"][tE
nQ2.Name], ShouldResemble, tEnQ2) | |
297 So(tq.GetTransactionTasks()["default"][0
], ShouldResemble, tEnQ3) | |
298 return nil | |
299 }, nil) | |
300 | |
301 // name gets generated at transaction-commit-tim
e | |
302 for name := range tq.GetScheduledTasks()["defaul
t"] { | |
303 if name == tEnQ.Name { | |
304 continue | |
305 } | |
306 tEnQ3.Name = name | |
307 break | |
308 } | |
309 | |
310 So(tq.GetScheduledTasks()["default"][tEnQ.Name],
ShouldResemble, tEnQ) | |
311 So(tq.GetScheduledTasks()["default"][tEnQ3.Name]
, ShouldResemble, tEnQ3) | |
312 So(tq.GetTombstonedTasks()["default"][tEnQ2.Name
], ShouldResemble, tEnQ2) | |
313 So(tq.GetTransactionTasks()["default"], ShouldBe
Nil) | |
314 }) | |
315 | |
316 Convey("can a new task (but reset the state in a test)",
func() { | |
317 tEnQ3 := (*taskqueue.Task)(nil) | |
318 | |
319 ttq := interface { | |
320 wrapper.TQTestable | |
321 wrapper.TaskQueue | |
322 }(nil) | |
323 | |
324 wrapper.GetDS(c).RunInTransaction(func(c context
.Context) error { | |
325 ttq = wrapper.GetTQ(c).(interface { | |
326 wrapper.TQTestable | |
327 wrapper.TaskQueue | |
328 }) | |
329 | |
330 t3 := &taskqueue.Task{Path: "/sandwitch/
victory"} | |
331 tEnQ3, err = ttq.Add(t3, "") | |
332 So(err, ShouldBeNil) | |
333 | |
334 So(ttq.GetScheduledTasks()["default"][tE
nQ.Name], ShouldResemble, tEnQ) | |
335 So(ttq.GetTombstonedTasks()["default"][t
EnQ2.Name], ShouldResemble, tEnQ2) | |
336 So(ttq.GetTransactionTasks()["default"][
0], ShouldResemble, tEnQ3) | |
337 | |
338 ttq.ResetTasks() | |
339 | |
340 So(len(ttq.GetScheduledTasks()["default"
]), ShouldEqual, 0) | |
341 So(len(ttq.GetTombstonedTasks()["default
"]), ShouldEqual, 0) | |
342 So(len(ttq.GetTransactionTasks()["defaul
t"]), ShouldEqual, 0) | |
343 | |
344 return nil | |
345 }, nil) | |
346 | |
347 So(len(tq.GetScheduledTasks()["default"]), Shoul
dEqual, 0) | |
348 So(len(tq.GetTombstonedTasks()["default"]), Shou
ldEqual, 0) | |
349 So(len(tq.GetTransactionTasks()["default"]), Sho
uldEqual, 0) | |
350 | |
351 Convey("and reusing a closed context is bad time
s", func() { | |
352 _, err := ttq.Add(nil, "") | |
353 So(err.Error(), ShouldContainSubstring,
"expired") | |
354 }) | |
355 }) | |
356 | |
357 Convey("you can AddMulti as well", func() { | |
358 wrapper.GetDS(c).RunInTransaction(func(c context
.Context) error { | |
359 tq := wrapper.GetTQ(c).(interface { | |
360 wrapper.TQTestable | |
361 wrapper.TaskQueue | |
362 }) | |
363 _, err := tq.AddMulti([]*taskqueue.Task{
t, t, t}, "") | |
364 So(err, ShouldBeNil) | |
365 So(len(tq.GetScheduledTasks()["default"]
), ShouldEqual, 1) | |
366 So(len(tq.GetTransactionTasks()["default
"]), ShouldEqual, 3) | |
367 return nil | |
368 }, nil) | |
369 So(len(tq.GetScheduledTasks()["default"]), Shoul
dEqual, 4) | |
370 So(len(tq.GetTransactionTasks()["default"]), Sho
uldEqual, 0) | |
371 }) | |
372 | |
373 Convey("unless you add too many things", func() { | |
374 wrapper.GetDS(c).RunInTransaction(func(c context
.Context) error { | |
375 for i := 0; i < 5; i++ { | |
376 _, err = wrapper.GetTQ(c).Add(t,
"") | |
377 So(err, ShouldBeNil) | |
378 } | |
379 _, err = wrapper.GetTQ(c).Add(t, "") | |
380 So(err.Error(), ShouldContainSubstring,
"BAD_REQUEST") | |
381 return nil | |
382 }, nil) | |
383 }) | |
384 | |
385 Convey("unless you Add to a bad queue", func() { | |
386 wrapper.GetDS(c).RunInTransaction(func(c context
.Context) error { | |
387 _, err = wrapper.GetTQ(c).Add(t, "meat") | |
388 So(err.Error(), ShouldContainSubstring,
"UNKNOWN_QUEUE") | |
389 | |
390 Convey("unless you add it!", func() { | |
391 wrapper.GetTQ(c).(wrapper.TQTest
able).CreateQueue("meat") | |
392 _, err = wrapper.GetTQ(c).Add(t,
"meat") | |
393 So(err, ShouldBeNil) | |
394 }) | |
395 | |
396 return nil | |
397 }, nil) | |
398 }) | |
399 | |
400 Convey("unless Add is broken", func() { | |
401 tq.BreakFeatures(nil, "Add") | |
402 wrapper.GetDS(c).RunInTransaction(func(c context
.Context) error { | |
403 _, err = wrapper.GetTQ(c).Add(t, "") | |
404 So(err.Error(), ShouldContainSubstring,
"TRANSIENT_ERROR") | |
405 return nil | |
406 }, nil) | |
407 }) | |
408 | |
409 Convey("unless AddMulti is broken", func() { | |
410 tq.BreakFeatures(nil, "AddMulti") | |
411 wrapper.GetDS(c).RunInTransaction(func(c context
.Context) error { | |
412 _, err = wrapper.GetTQ(c).AddMulti(nil,
"") | |
413 So(err.Error(), ShouldContainSubstring,
"TRANSIENT_ERROR") | |
414 return nil | |
415 }, nil) | |
416 }) | |
417 | |
418 Convey("No other features are available, however", func(
) { | |
419 err := error(nil) | |
420 func() { | |
421 defer func() { err = recover().(error) }
() | |
422 wrapper.GetDS(c).RunInTransaction(func(c
context.Context) error { | |
423 wrapper.GetTQ(c).Delete(t, "") | |
424 return nil | |
425 }, nil) | |
426 }() | |
427 So(err.Error(), ShouldContainSubstring, "TaskQue
ue.Delete") | |
428 }) | |
429 | |
430 Convey("adding a new task only happens if we don't errou
t", func() { | |
431 wrapper.GetDS(c).RunInTransaction(func(c context
.Context) error { | |
432 t3 := &taskqueue.Task{Path: "/sandwitch/
victory"} | |
433 _, err = wrapper.GetTQ(c).Add(t3, "") | |
434 So(err, ShouldBeNil) | |
435 return fmt.Errorf("nooooo") | |
436 }, nil) | |
437 | |
438 So(tq.GetScheduledTasks()["default"][tEnQ.Name],
ShouldResemble, tEnQ) | |
439 So(tq.GetTombstonedTasks()["default"][tEnQ2.Name
], ShouldResemble, tEnQ2) | |
440 So(tq.GetTransactionTasks()["default"], ShouldBe
Nil) | |
441 }) | |
442 | |
443 Convey("likewise, a panic doesn't schedule anything", fu
nc() { | |
444 func() { | |
445 defer func() { recover() }() | |
446 wrapper.GetDS(c).RunInTransaction(func(c
context.Context) error { | |
447 tq := wrapper.GetTQ(c).(interfac
e { | |
448 wrapper.TQTestable | |
449 wrapper.TaskQueue | |
450 }) | |
451 | |
452 t3 := &taskqueue.Task{Path: "/sa
ndwitch/victory"} | |
453 _, err = tq.Add(t3, "") | |
454 So(err, ShouldBeNil) | |
455 | |
456 panic(fmt.Errorf("nooooo")) | |
457 }, nil) | |
458 }() | |
459 | |
460 So(tq.GetScheduledTasks()["default"][tEnQ.Name],
ShouldResemble, tEnQ) | |
461 So(tq.GetTombstonedTasks()["default"][tEnQ2.Name
], ShouldResemble, tEnQ2) | |
462 So(tq.GetTransactionTasks()["default"], ShouldBe
Nil) | |
463 }) | |
464 | |
465 }) | |
466 }) | |
467 } | |
OLD | NEW |