OLD | NEW |
| (Empty) |
1 package db | |
2 | |
3 import ( | |
4 "fmt" | |
5 "net/url" | |
6 "testing" | |
7 "time" | |
8 | |
9 assert "github.com/stretchr/testify/require" | |
10 | |
11 "go.skia.org/infra/go/testutils" | |
12 "go.skia.org/infra/go/util" | |
13 ) | |
14 | |
15 const DEFAULT_TEST_REPO = "go-on-now.git" | |
16 | |
17 func makeTask(ts time.Time, commits []string) *Task { | |
18 return &Task{ | |
19 Created: ts, | |
20 Repo: DEFAULT_TEST_REPO, | |
21 Commits: commits, | |
22 Name: "Test-Task", | |
23 } | |
24 } | |
25 | |
26 func TestDB(t *testing.T, db DB) { | |
27 defer testutils.AssertCloses(t, db) | |
28 | |
29 _, err := db.GetModifiedTasks("dummy-id") | |
30 assert.True(t, IsUnknownId(err)) | |
31 | |
32 id, err := db.StartTrackingModifiedTasks() | |
33 assert.NoError(t, err) | |
34 | |
35 tasks, err := db.GetModifiedTasks(id) | |
36 assert.NoError(t, err) | |
37 assert.Equal(t, 0, len(tasks)) | |
38 | |
39 t1 := makeTask(time.Time{}, []string{"a", "b", "c", "d"}) | |
40 | |
41 // AssignId should fill in t1.Id. | |
42 assert.Equal(t, "", t1.Id) | |
43 assert.NoError(t, db.AssignId(t1)) | |
44 assert.NotEqual(t, "", t1.Id) | |
45 // Ids must be URL-safe. | |
46 assert.Equal(t, url.QueryEscape(t1.Id), t1.Id) | |
47 | |
48 // Task doesn't exist in DB yet. | |
49 noTask, err := db.GetTaskById(t1.Id) | |
50 assert.NoError(t, err) | |
51 assert.Nil(t, noTask) | |
52 | |
53 // Set Creation time. Ensure Created is not the time of AssignId to test
the | |
54 // sequence (1) AssignId, (2) initialize task, (3) PutTask. | |
55 now := time.Now().Add(time.Nanosecond) | |
56 t1.Created = now | |
57 | |
58 // Insert the task. | |
59 assert.NoError(t, db.PutTask(t1)) | |
60 | |
61 // Check that DbModified was set. | |
62 assert.False(t, util.TimeIsZero(t1.DbModified)) | |
63 t1LastModified := t1.DbModified | |
64 | |
65 // Task can now be retrieved by Id. | |
66 t1Again, err := db.GetTaskById(t1.Id) | |
67 assert.NoError(t, err) | |
68 testutils.AssertDeepEqual(t, t1, t1Again) | |
69 | |
70 // Ensure that the task shows up in the modified list. | |
71 tasks, err = db.GetModifiedTasks(id) | |
72 assert.NoError(t, err) | |
73 testutils.AssertDeepEqual(t, []*Task{t1}, tasks) | |
74 | |
75 // Ensure that the task shows up in the correct date ranges. | |
76 timeStart := time.Time{} | |
77 t1Before := t1.Created | |
78 t1After := t1Before.Add(1 * time.Nanosecond) | |
79 timeEnd := now.Add(2 * time.Nanosecond) | |
80 tasks, err = db.GetTasksFromDateRange(timeStart, t1Before) | |
81 assert.NoError(t, err) | |
82 assert.Equal(t, 0, len(tasks)) | |
83 tasks, err = db.GetTasksFromDateRange(t1Before, t1After) | |
84 assert.NoError(t, err) | |
85 testutils.AssertDeepEqual(t, []*Task{t1}, tasks) | |
86 tasks, err = db.GetTasksFromDateRange(t1After, timeEnd) | |
87 assert.NoError(t, err) | |
88 assert.Equal(t, 0, len(tasks)) | |
89 | |
90 // Insert two more tasks. Ensure at least 1 nanosecond between task Crea
ted | |
91 // times so that t1After != t2Before and t2After != t3Before. | |
92 t2 := makeTask(now.Add(time.Nanosecond), []string{"e", "f"}) | |
93 t3 := makeTask(now.Add(2*time.Nanosecond), []string{"g", "h"}) | |
94 assert.NoError(t, db.PutTasks([]*Task{t2, t3})) | |
95 | |
96 // Check that PutTasks assigned Ids. | |
97 assert.NotEqual(t, "", t2.Id) | |
98 assert.NotEqual(t, "", t3.Id) | |
99 // Ids must be URL-safe. | |
100 assert.Equal(t, url.QueryEscape(t2.Id), t2.Id) | |
101 assert.Equal(t, url.QueryEscape(t3.Id), t3.Id) | |
102 | |
103 // Ensure that both tasks show up in the modified list. | |
104 tasks, err = db.GetModifiedTasks(id) | |
105 assert.NoError(t, err) | |
106 testutils.AssertDeepEqual(t, []*Task{t2, t3}, tasks) | |
107 | |
108 // Make an update to t1 and t2. Ensure modified times change. | |
109 t2LastModified := t2.DbModified | |
110 t1.Status = TASK_STATUS_RUNNING | |
111 t2.Status = TASK_STATUS_SUCCESS | |
112 assert.NoError(t, db.PutTasks([]*Task{t1, t2})) | |
113 assert.False(t, t1.DbModified.Equal(t1LastModified)) | |
114 assert.False(t, t2.DbModified.Equal(t2LastModified)) | |
115 | |
116 // Ensure that both tasks show up in the modified list. | |
117 tasks, err = db.GetModifiedTasks(id) | |
118 assert.NoError(t, err) | |
119 testutils.AssertDeepEqual(t, []*Task{t1, t2}, tasks) | |
120 | |
121 // Ensure that all tasks show up in the correct time ranges, in sorted o
rder. | |
122 t2Before := t2.Created | |
123 t2After := t2Before.Add(1 * time.Nanosecond) | |
124 | |
125 t3Before := t3.Created | |
126 t3After := t3Before.Add(1 * time.Nanosecond) | |
127 | |
128 timeEnd = now.Add(3 * time.Nanosecond) | |
129 | |
130 tasks, err = db.GetTasksFromDateRange(timeStart, t1Before) | |
131 assert.NoError(t, err) | |
132 assert.Equal(t, 0, len(tasks)) | |
133 | |
134 tasks, err = db.GetTasksFromDateRange(timeStart, t1After) | |
135 assert.NoError(t, err) | |
136 testutils.AssertDeepEqual(t, []*Task{t1}, tasks) | |
137 | |
138 tasks, err = db.GetTasksFromDateRange(timeStart, t2Before) | |
139 assert.NoError(t, err) | |
140 testutils.AssertDeepEqual(t, []*Task{t1}, tasks) | |
141 | |
142 tasks, err = db.GetTasksFromDateRange(timeStart, t2After) | |
143 assert.NoError(t, err) | |
144 testutils.AssertDeepEqual(t, []*Task{t1, t2}, tasks) | |
145 | |
146 tasks, err = db.GetTasksFromDateRange(timeStart, t3Before) | |
147 assert.NoError(t, err) | |
148 testutils.AssertDeepEqual(t, []*Task{t1, t2}, tasks) | |
149 | |
150 tasks, err = db.GetTasksFromDateRange(timeStart, t3After) | |
151 assert.NoError(t, err) | |
152 testutils.AssertDeepEqual(t, []*Task{t1, t2, t3}, tasks) | |
153 | |
154 tasks, err = db.GetTasksFromDateRange(timeStart, timeEnd) | |
155 assert.NoError(t, err) | |
156 testutils.AssertDeepEqual(t, []*Task{t1, t2, t3}, tasks) | |
157 | |
158 tasks, err = db.GetTasksFromDateRange(t1Before, timeEnd) | |
159 assert.NoError(t, err) | |
160 testutils.AssertDeepEqual(t, []*Task{t1, t2, t3}, tasks) | |
161 | |
162 tasks, err = db.GetTasksFromDateRange(t1After, timeEnd) | |
163 assert.NoError(t, err) | |
164 testutils.AssertDeepEqual(t, []*Task{t2, t3}, tasks) | |
165 | |
166 tasks, err = db.GetTasksFromDateRange(t2Before, timeEnd) | |
167 assert.NoError(t, err) | |
168 testutils.AssertDeepEqual(t, []*Task{t2, t3}, tasks) | |
169 | |
170 tasks, err = db.GetTasksFromDateRange(t2After, timeEnd) | |
171 assert.NoError(t, err) | |
172 testutils.AssertDeepEqual(t, []*Task{t3}, tasks) | |
173 | |
174 tasks, err = db.GetTasksFromDateRange(t3Before, timeEnd) | |
175 assert.NoError(t, err) | |
176 testutils.AssertDeepEqual(t, []*Task{t3}, tasks) | |
177 | |
178 tasks, err = db.GetTasksFromDateRange(t3After, timeEnd) | |
179 assert.NoError(t, err) | |
180 testutils.AssertDeepEqual(t, []*Task{}, tasks) | |
181 } | |
182 | |
183 func TestTooManyUsers(t *testing.T, db DB) { | |
184 defer testutils.AssertCloses(t, db) | |
185 | |
186 // Max out the number of modified-tasks users; ensure that we error out. | |
187 for i := 0; i < MAX_MODIFIED_TASKS_USERS; i++ { | |
188 _, err := db.StartTrackingModifiedTasks() | |
189 assert.NoError(t, err) | |
190 } | |
191 _, err := db.StartTrackingModifiedTasks() | |
192 assert.True(t, IsTooManyUsers(err)) | |
193 } | |
194 | |
195 // Test that PutTask and PutTasks return ErrConcurrentUpdate when a cached Task | |
196 // has been updated in the DB. | |
197 func TestConcurrentUpdate(t *testing.T, db DB) { | |
198 defer testutils.AssertCloses(t, db) | |
199 | |
200 // Insert a task. | |
201 t1 := makeTask(time.Now(), []string{"a", "b", "c", "d"}) | |
202 assert.NoError(t, db.PutTask(t1)) | |
203 | |
204 // Retrieve a copy of the task. | |
205 t1Cached, err := db.GetTaskById(t1.Id) | |
206 assert.NoError(t, err) | |
207 testutils.AssertDeepEqual(t, t1, t1Cached) | |
208 | |
209 // Update the original task. | |
210 t1.Commits = []string{"a", "b"} | |
211 assert.NoError(t, db.PutTask(t1)) | |
212 | |
213 // Update the cached copy; should get concurrent update error. | |
214 t1Cached.Status = TASK_STATUS_RUNNING | |
215 err = db.PutTask(t1Cached) | |
216 assert.True(t, IsConcurrentUpdate(err)) | |
217 | |
218 { | |
219 // DB should still have the old value of t1. | |
220 t1Again, err := db.GetTaskById(t1.Id) | |
221 assert.NoError(t, err) | |
222 testutils.AssertDeepEqual(t, t1, t1Again) | |
223 } | |
224 | |
225 // Insert a second task. | |
226 t2 := makeTask(time.Now(), []string{"e", "f"}) | |
227 assert.NoError(t, db.PutTask(t2)) | |
228 | |
229 // Update t2 at the same time as t1Cached; should still get an error. | |
230 t2.Status = TASK_STATUS_MISHAP | |
231 err = db.PutTasks([]*Task{t2, t1Cached}) | |
232 assert.True(t, IsConcurrentUpdate(err)) | |
233 | |
234 { | |
235 // DB should still have the old value of t1. | |
236 t1Again, err := db.GetTaskById(t1.Id) | |
237 assert.NoError(t, err) | |
238 testutils.AssertDeepEqual(t, t1, t1Again) | |
239 | |
240 // DB should also still have the old value of t2, but to keep In
MemoryDB | |
241 // simple, we don't check that here. | |
242 } | |
243 } | |
244 | |
245 // Test UpdateWithRetries when no errors or retries. | |
246 func testUpdateWithRetriesSimple(t *testing.T, db DB) { | |
247 begin := time.Now() | |
248 | |
249 // Test no-op. | |
250 tasks, err := UpdateWithRetries(db, func() ([]*Task, error) { | |
251 return nil, nil | |
252 }) | |
253 assert.NoError(t, err) | |
254 assert.Equal(t, 0, len(tasks)) | |
255 | |
256 // Create new task t1. (UpdateWithRetries isn't actually useful in this
case.) | |
257 tasks, err = UpdateWithRetries(db, func() ([]*Task, error) { | |
258 t1 := makeTask(time.Time{}, []string{"a", "b", "c", "d"}) | |
259 assert.NoError(t, db.AssignId(t1)) | |
260 t1.Created = time.Now().Add(time.Nanosecond) | |
261 return []*Task{t1}, nil | |
262 }) | |
263 assert.NoError(t, err) | |
264 assert.Equal(t, 1, len(tasks)) | |
265 t1 := tasks[0] | |
266 | |
267 // Update t1 and create t2. | |
268 tasks, err = UpdateWithRetries(db, func() ([]*Task, error) { | |
269 t1, err := db.GetTaskById(t1.Id) | |
270 assert.NoError(t, err) | |
271 t1.Status = TASK_STATUS_RUNNING | |
272 t2 := makeTask(t1.Created.Add(time.Nanosecond), []string{"e", "f
"}) | |
273 return []*Task{t1, t2}, nil | |
274 }) | |
275 assert.NoError(t, err) | |
276 assert.Equal(t, 2, len(tasks)) | |
277 assert.Equal(t, t1.Id, tasks[0].Id) | |
278 assert.Equal(t, TASK_STATUS_RUNNING, tasks[0].Status) | |
279 assert.Equal(t, []string{"e", "f"}, tasks[1].Commits) | |
280 | |
281 // Check that return value matches what's in the DB. | |
282 t1, err = db.GetTaskById(t1.Id) | |
283 assert.NoError(t, err) | |
284 t2, err := db.GetTaskById(tasks[1].Id) | |
285 assert.NoError(t, err) | |
286 testutils.AssertDeepEqual(t, tasks[0], t1) | |
287 testutils.AssertDeepEqual(t, tasks[1], t2) | |
288 | |
289 // Check no extra tasks in the DB. | |
290 tasks, err = db.GetTasksFromDateRange(begin, time.Now().Add(3*time.Nanos
econd)) | |
291 assert.NoError(t, err) | |
292 assert.Equal(t, 2, len(tasks)) | |
293 assert.Equal(t, t1.Id, tasks[0].Id) | |
294 assert.Equal(t, t2.Id, tasks[1].Id) | |
295 } | |
296 | |
297 // Test UpdateWithRetries when there are some retries, but eventual success. | |
298 func testUpdateWithRetriesSuccess(t *testing.T, db DB) { | |
299 begin := time.Now() | |
300 | |
301 // Create and cache. | |
302 t1 := makeTask(begin.Add(time.Nanosecond), []string{"a", "b", "c", "d"}) | |
303 assert.NoError(t, db.PutTask(t1)) | |
304 t1Cached := t1.Copy() | |
305 | |
306 // Update original. | |
307 t1.Status = TASK_STATUS_RUNNING | |
308 assert.NoError(t, db.PutTask(t1)) | |
309 | |
310 // Attempt update. | |
311 callCount := 0 | |
312 tasks, err := UpdateWithRetries(db, func() ([]*Task, error) { | |
313 callCount++ | |
314 if callCount >= 3 { | |
315 if task, err := db.GetTaskById(t1.Id); err != nil { | |
316 return nil, err | |
317 } else { | |
318 t1Cached = task | |
319 } | |
320 } | |
321 t1Cached.Status = TASK_STATUS_SUCCESS | |
322 t2 := makeTask(begin.Add(2*time.Nanosecond), []string{"e", "f"}) | |
323 return []*Task{t1Cached, t2}, nil | |
324 }) | |
325 assert.NoError(t, err) | |
326 assert.Equal(t, 3, callCount) | |
327 assert.Equal(t, 2, len(tasks)) | |
328 assert.Equal(t, t1.Id, tasks[0].Id) | |
329 assert.Equal(t, TASK_STATUS_SUCCESS, tasks[0].Status) | |
330 assert.Equal(t, []string{"e", "f"}, tasks[1].Commits) | |
331 | |
332 // Check that return value matches what's in the DB. | |
333 t1, err = db.GetTaskById(t1.Id) | |
334 assert.NoError(t, err) | |
335 t2, err := db.GetTaskById(tasks[1].Id) | |
336 assert.NoError(t, err) | |
337 testutils.AssertDeepEqual(t, tasks[0], t1) | |
338 testutils.AssertDeepEqual(t, tasks[1], t2) | |
339 | |
340 // Check no extra tasks in the DB. | |
341 tasks, err = db.GetTasksFromDateRange(begin, time.Now().Add(3*time.Nanos
econd)) | |
342 assert.NoError(t, err) | |
343 assert.Equal(t, 2, len(tasks)) | |
344 assert.Equal(t, t1.Id, tasks[0].Id) | |
345 assert.Equal(t, t2.Id, tasks[1].Id) | |
346 } | |
347 | |
348 // Test UpdateWithRetries when f returns an error. | |
349 func testUpdateWithRetriesErrorInFunc(t *testing.T, db DB) { | |
350 begin := time.Now() | |
351 | |
352 myErr := fmt.Errorf("NO! Bad dog!") | |
353 callCount := 0 | |
354 tasks, err := UpdateWithRetries(db, func() ([]*Task, error) { | |
355 callCount++ | |
356 // Return a task just for fun. | |
357 return []*Task{ | |
358 makeTask(begin.Add(time.Nanosecond), []string{"a", "b",
"c", "d"}), | |
359 }, myErr | |
360 }) | |
361 assert.Error(t, err) | |
362 assert.Equal(t, myErr, err) | |
363 assert.Equal(t, 0, len(tasks)) | |
364 assert.Equal(t, 1, callCount) | |
365 | |
366 // Check no tasks in the DB. | |
367 tasks, err = db.GetTasksFromDateRange(begin, time.Now().Add(2*time.Nanos
econd)) | |
368 assert.NoError(t, err) | |
369 assert.Equal(t, 0, len(tasks)) | |
370 } | |
371 | |
372 // Test UpdateWithRetries when PutTasks returns an error. | |
373 func testUpdateWithRetriesErrorInPutTasks(t *testing.T, db DB) { | |
374 begin := time.Now() | |
375 | |
376 callCount := 0 | |
377 tasks, err := UpdateWithRetries(db, func() ([]*Task, error) { | |
378 callCount++ | |
379 // Task has zero Created time. | |
380 return []*Task{ | |
381 makeTask(time.Time{}, []string{"a", "b", "c", "d"}), | |
382 }, nil | |
383 }) | |
384 assert.Error(t, err) | |
385 assert.Contains(t, err.Error(), "Created not set.") | |
386 assert.Equal(t, 0, len(tasks)) | |
387 assert.Equal(t, 1, callCount) | |
388 | |
389 // Check no tasks in the DB. | |
390 tasks, err = db.GetTasksFromDateRange(begin, time.Now().Add(time.Nanosec
ond)) | |
391 assert.NoError(t, err) | |
392 assert.Equal(t, 0, len(tasks)) | |
393 } | |
394 | |
395 // Test UpdateWithRetries when retries are exhausted. | |
396 func testUpdateWithRetriesExhausted(t *testing.T, db DB) { | |
397 begin := time.Now() | |
398 | |
399 // Create and cache. | |
400 t1 := makeTask(begin.Add(time.Nanosecond), []string{"a", "b", "c", "d"}) | |
401 assert.NoError(t, db.PutTask(t1)) | |
402 t1Cached := t1.Copy() | |
403 | |
404 // Update original. | |
405 t1.Status = TASK_STATUS_RUNNING | |
406 assert.NoError(t, db.PutTask(t1)) | |
407 | |
408 // Attempt update. | |
409 callCount := 0 | |
410 tasks, err := UpdateWithRetries(db, func() ([]*Task, error) { | |
411 callCount++ | |
412 t1Cached.Status = TASK_STATUS_SUCCESS | |
413 t2 := makeTask(begin.Add(2*time.Nanosecond), []string{"e", "f"}) | |
414 return []*Task{t1Cached, t2}, nil | |
415 }) | |
416 assert.True(t, IsConcurrentUpdate(err)) | |
417 assert.Equal(t, NUM_RETRIES, callCount) | |
418 assert.Equal(t, 0, len(tasks)) | |
419 | |
420 // Check no extra tasks in the DB. | |
421 tasks, err = db.GetTasksFromDateRange(begin, time.Now().Add(3*time.Nanos
econd)) | |
422 assert.NoError(t, err) | |
423 assert.Equal(t, 1, len(tasks)) | |
424 assert.Equal(t, t1.Id, tasks[0].Id) | |
425 assert.Equal(t, TASK_STATUS_RUNNING, tasks[0].Status) | |
426 } | |
427 | |
428 // Test UpdateTaskWithRetries when no errors or retries. | |
429 func testUpdateTaskWithRetriesSimple(t *testing.T, db DB) { | |
430 begin := time.Now() | |
431 | |
432 // Create new task t1. | |
433 t1 := makeTask(time.Time{}, []string{"a", "b", "c", "d"}) | |
434 assert.NoError(t, db.AssignId(t1)) | |
435 t1.Created = time.Now().Add(time.Nanosecond) | |
436 assert.NoError(t, db.PutTask(t1)) | |
437 | |
438 // Update t1. | |
439 t1Updated, err := UpdateTaskWithRetries(db, t1.Id, func(task *Task) erro
r { | |
440 task.Status = TASK_STATUS_RUNNING | |
441 return nil | |
442 }) | |
443 assert.NoError(t, err) | |
444 assert.Equal(t, t1.Id, t1Updated.Id) | |
445 assert.Equal(t, TASK_STATUS_RUNNING, t1Updated.Status) | |
446 assert.NotEqual(t, t1.DbModified, t1Updated.DbModified) | |
447 | |
448 // Check that return value matches what's in the DB. | |
449 t1Again, err := db.GetTaskById(t1.Id) | |
450 assert.NoError(t, err) | |
451 testutils.AssertDeepEqual(t, t1Again, t1Updated) | |
452 | |
453 // Check no extra tasks in the DB. | |
454 tasks, err := db.GetTasksFromDateRange(begin, time.Now().Add(2*time.Nano
second)) | |
455 assert.NoError(t, err) | |
456 assert.Equal(t, 1, len(tasks)) | |
457 assert.Equal(t, t1.Id, tasks[0].Id) | |
458 } | |
459 | |
460 // Test UpdateTaskWithRetries when there are some retries, but eventual success. | |
461 func testUpdateTaskWithRetriesSuccess(t *testing.T, db DB) { | |
462 begin := time.Now() | |
463 | |
464 // Create new task t1. | |
465 t1 := makeTask(begin.Add(time.Nanosecond), []string{"a", "b", "c", "d"}) | |
466 assert.NoError(t, db.PutTask(t1)) | |
467 | |
468 // Attempt update. | |
469 callCount := 0 | |
470 t1Updated, err := UpdateTaskWithRetries(db, t1.Id, func(task *Task) erro
r { | |
471 callCount++ | |
472 if callCount < 3 { | |
473 // Sneakily make an update in the background. | |
474 t1.Commits = append(t1.Commits, fmt.Sprintf("z%d", callC
ount)) | |
475 assert.NoError(t, db.PutTask(t1)) | |
476 } | |
477 task.Status = TASK_STATUS_SUCCESS | |
478 return nil | |
479 }) | |
480 assert.NoError(t, err) | |
481 assert.Equal(t, 3, callCount) | |
482 assert.Equal(t, t1.Id, t1Updated.Id) | |
483 assert.Equal(t, TASK_STATUS_SUCCESS, t1Updated.Status) | |
484 | |
485 // Check that return value matches what's in the DB. | |
486 t1Again, err := db.GetTaskById(t1.Id) | |
487 assert.NoError(t, err) | |
488 testutils.AssertDeepEqual(t, t1Again, t1Updated) | |
489 | |
490 // Check no extra tasks in the DB. | |
491 tasks, err := db.GetTasksFromDateRange(begin, time.Now().Add(2*time.Nano
second)) | |
492 assert.NoError(t, err) | |
493 assert.Equal(t, 1, len(tasks)) | |
494 assert.Equal(t, t1.Id, tasks[0].Id) | |
495 } | |
496 | |
497 // Test UpdateTaskWithRetries when f returns an error. | |
498 func testUpdateTaskWithRetriesErrorInFunc(t *testing.T, db DB) { | |
499 begin := time.Now() | |
500 | |
501 // Create new task t1. | |
502 t1 := makeTask(begin.Add(time.Nanosecond), []string{"a", "b", "c", "d"}) | |
503 assert.NoError(t, db.PutTask(t1)) | |
504 | |
505 // Update and return an error. | |
506 myErr := fmt.Errorf("Um, actually, I didn't want to update that task.") | |
507 callCount := 0 | |
508 noTask, err := UpdateTaskWithRetries(db, t1.Id, func(task *Task) error { | |
509 callCount++ | |
510 // Update task to test nothing changes in DB. | |
511 task.Status = TASK_STATUS_RUNNING | |
512 return myErr | |
513 }) | |
514 assert.Error(t, err) | |
515 assert.Equal(t, myErr, err) | |
516 assert.Nil(t, noTask) | |
517 assert.Equal(t, 1, callCount) | |
518 | |
519 // Check task did not change in the DB. | |
520 t1Again, err := db.GetTaskById(t1.Id) | |
521 assert.NoError(t, err) | |
522 testutils.AssertDeepEqual(t, t1, t1Again) | |
523 | |
524 // Check no extra tasks in the DB. | |
525 tasks, err := db.GetTasksFromDateRange(begin, time.Now().Add(2*time.Nano
second)) | |
526 assert.NoError(t, err) | |
527 assert.Equal(t, 1, len(tasks)) | |
528 assert.Equal(t, t1.Id, tasks[0].Id) | |
529 } | |
530 | |
531 // Test UpdateTaskWithRetries when retries are exhausted. | |
532 func testUpdateTaskWithRetriesExhausted(t *testing.T, db DB) { | |
533 begin := time.Now() | |
534 | |
535 // Create new task t1. | |
536 t1 := makeTask(begin.Add(time.Nanosecond), []string{"a", "b", "c", "d"}) | |
537 assert.NoError(t, db.PutTask(t1)) | |
538 | |
539 // Update original. | |
540 t1.Status = TASK_STATUS_RUNNING | |
541 assert.NoError(t, db.PutTask(t1)) | |
542 | |
543 // Attempt update. | |
544 callCount := 0 | |
545 noTask, err := UpdateTaskWithRetries(db, t1.Id, func(task *Task) error { | |
546 callCount++ | |
547 // Sneakily make an update in the background. | |
548 t1.Commits = append(t1.Commits, fmt.Sprintf("z%d", callCount)) | |
549 assert.NoError(t, db.PutTask(t1)) | |
550 | |
551 task.Status = TASK_STATUS_SUCCESS | |
552 return nil | |
553 }) | |
554 assert.True(t, IsConcurrentUpdate(err)) | |
555 assert.Equal(t, NUM_RETRIES, callCount) | |
556 assert.Nil(t, noTask) | |
557 | |
558 // Check task did not change in the DB. | |
559 t1Again, err := db.GetTaskById(t1.Id) | |
560 assert.NoError(t, err) | |
561 testutils.AssertDeepEqual(t, t1, t1Again) | |
562 | |
563 // Check no extra tasks in the DB. | |
564 tasks, err := db.GetTasksFromDateRange(begin, time.Now().Add(2*time.Nano
second)) | |
565 assert.NoError(t, err) | |
566 assert.Equal(t, 1, len(tasks)) | |
567 assert.Equal(t, t1.Id, tasks[0].Id) | |
568 } | |
569 | |
570 // Test UpdateTaskWithRetries when the given ID is not found in the DB. | |
571 func testUpdateTaskWithRetriesTaskNotFound(t *testing.T, db DB) { | |
572 begin := time.Now() | |
573 | |
574 // Assign ID for a task, but don't put it in the DB. | |
575 t1 := makeTask(begin.Add(time.Nanosecond), []string{"a", "b", "c", "d"}) | |
576 assert.NoError(t, db.AssignId(t1)) | |
577 | |
578 // Attempt to update non-existent task. Function shouldn't be called. | |
579 callCount := 0 | |
580 noTask, err := UpdateTaskWithRetries(db, t1.Id, func(task *Task) error { | |
581 callCount++ | |
582 task.Status = TASK_STATUS_RUNNING | |
583 return nil | |
584 }) | |
585 assert.True(t, IsNotFound(err)) | |
586 assert.Nil(t, noTask) | |
587 assert.Equal(t, 0, callCount) | |
588 | |
589 // Check no tasks in the DB. | |
590 tasks, err := db.GetTasksFromDateRange(begin, time.Now().Add(2*time.Nano
second)) | |
591 assert.NoError(t, err) | |
592 assert.Equal(t, 0, len(tasks)) | |
593 } | |
594 | |
595 // Test UpdateWithRetries and UpdateTaskWithRetries. | |
596 func TestUpdateWithRetries(t *testing.T, db DB) { | |
597 testUpdateWithRetriesSimple(t, db) | |
598 testUpdateWithRetriesSuccess(t, db) | |
599 testUpdateWithRetriesErrorInFunc(t, db) | |
600 testUpdateWithRetriesErrorInPutTasks(t, db) | |
601 testUpdateWithRetriesExhausted(t, db) | |
602 testUpdateTaskWithRetriesSimple(t, db) | |
603 testUpdateTaskWithRetriesSuccess(t, db) | |
604 testUpdateTaskWithRetriesErrorInFunc(t, db) | |
605 testUpdateTaskWithRetriesExhausted(t, db) | |
606 testUpdateTaskWithRetriesTaskNotFound(t, db) | |
607 } | |
608 | |
609 // makeTaskComment creates a comment with its ID fields based on the given repo, | |
610 // name, commit, and ts, and other fields based on n. | |
611 func makeTaskComment(n int, repo int, name int, commit int, ts time.Time) *TaskC
omment { | |
612 return &TaskComment{ | |
613 Repo: fmt.Sprintf("r%d", repo), | |
614 Name: fmt.Sprintf("n%d", name), | |
615 Commit: fmt.Sprintf("c%d", commit), | |
616 Timestamp: ts, | |
617 TaskId: fmt.Sprintf("id%d", n), | |
618 User: fmt.Sprintf("u%d", n), | |
619 Message: fmt.Sprintf("m%d", n), | |
620 } | |
621 } | |
622 | |
623 // makeTaskSpecComment creates a comment with its ID fields based on the given | |
624 // repo, name, and ts, and other fields based on n. | |
625 func makeTaskSpecComment(n int, repo int, name int, ts time.Time) *TaskSpecComme
nt { | |
626 return &TaskSpecComment{ | |
627 Repo: fmt.Sprintf("r%d", repo), | |
628 Name: fmt.Sprintf("n%d", name), | |
629 Timestamp: ts, | |
630 User: fmt.Sprintf("u%d", n), | |
631 Flaky: n%2 == 0, | |
632 IgnoreFailure: n>>1%2 == 0, | |
633 Message: fmt.Sprintf("m%d", n), | |
634 } | |
635 } | |
636 | |
637 // makeCommitComment creates a comment with its ID fields based on the given | |
638 // repo, commit, and ts, and other fields based on n. | |
639 func makeCommitComment(n int, repo int, commit int, ts time.Time) *CommitComment
{ | |
640 return &CommitComment{ | |
641 Repo: fmt.Sprintf("r%d", repo), | |
642 Commit: fmt.Sprintf("c%d", commit), | |
643 Timestamp: ts, | |
644 User: fmt.Sprintf("u%d", n), | |
645 Message: fmt.Sprintf("m%d", n), | |
646 } | |
647 } | |
648 | |
649 // TestCommentDB validates that db correctly implements the CommentDB interface. | |
650 func TestCommentDB(t *testing.T, db CommentDB) { | |
651 now := time.Now() | |
652 | |
653 // Empty db. | |
654 { | |
655 actual, err := db.GetCommentsForRepos([]string{"r0", "r1", "r2"}
, now.Add(-10000*time.Hour)) | |
656 assert.NoError(t, err) | |
657 assert.Equal(t, 3, len(actual)) | |
658 assert.Equal(t, "r0", actual[0].Repo) | |
659 assert.Equal(t, "r1", actual[1].Repo) | |
660 assert.Equal(t, "r2", actual[2].Repo) | |
661 for _, rc := range actual { | |
662 assert.Equal(t, 0, len(rc.TaskComments)) | |
663 assert.Equal(t, 0, len(rc.TaskSpecComments)) | |
664 assert.Equal(t, 0, len(rc.CommitComments)) | |
665 } | |
666 } | |
667 | |
668 // Add some comments. | |
669 tc1 := makeTaskComment(1, 1, 1, 1, now) | |
670 tc2 := makeTaskComment(2, 1, 1, 1, now.Add(2*time.Second)) | |
671 tc3 := makeTaskComment(3, 1, 1, 1, now.Add(time.Second)) | |
672 tc4 := makeTaskComment(4, 1, 1, 2, now) | |
673 tc5 := makeTaskComment(5, 1, 2, 2, now) | |
674 tc6 := makeTaskComment(6, 2, 3, 3, now) | |
675 tc6copy := tc6.Copy() // Adding identical comment should be ignored. | |
676 for _, c := range []*TaskComment{tc1, tc2, tc3, tc4, tc5, tc6, tc6copy}
{ | |
677 assert.NoError(t, db.PutTaskComment(c)) | |
678 } | |
679 tc6.Message = "modifying after Put shouldn't affect stored comment" | |
680 | |
681 sc1 := makeTaskSpecComment(1, 1, 1, now) | |
682 sc2 := makeTaskSpecComment(2, 1, 1, now.Add(2*time.Second)) | |
683 sc3 := makeTaskSpecComment(3, 1, 1, now.Add(time.Second)) | |
684 sc4 := makeTaskSpecComment(4, 1, 2, now) | |
685 sc5 := makeTaskSpecComment(5, 2, 3, now) | |
686 sc5copy := sc5.Copy() // Adding identical comment should be ignored. | |
687 for _, c := range []*TaskSpecComment{sc1, sc2, sc3, sc4, sc5, sc5copy} { | |
688 assert.NoError(t, db.PutTaskSpecComment(c)) | |
689 } | |
690 sc5.Message = "modifying after Put shouldn't affect stored comment" | |
691 | |
692 cc1 := makeCommitComment(1, 1, 1, now) | |
693 cc2 := makeCommitComment(2, 1, 1, now.Add(2*time.Second)) | |
694 cc3 := makeCommitComment(3, 1, 1, now.Add(time.Second)) | |
695 cc4 := makeCommitComment(4, 1, 2, now) | |
696 cc5 := makeCommitComment(5, 2, 3, now) | |
697 cc5copy := cc5.Copy() // Adding identical comment should be ignored. | |
698 for _, c := range []*CommitComment{cc1, cc2, cc3, cc4, cc5, cc5copy} { | |
699 assert.NoError(t, db.PutCommitComment(c)) | |
700 } | |
701 cc5.Message = "modifying after Put shouldn't affect stored comment" | |
702 | |
703 // Check that adding duplicate non-identical comment gives an error. | |
704 tc1different := tc1.Copy() | |
705 tc1different.Message = "not the same" | |
706 assert.True(t, IsAlreadyExists(db.PutTaskComment(tc1different))) | |
707 sc1different := sc1.Copy() | |
708 sc1different.Message = "not the same" | |
709 assert.True(t, IsAlreadyExists(db.PutTaskSpecComment(sc1different))) | |
710 cc1different := cc1.Copy() | |
711 cc1different.Message = "not the same" | |
712 assert.True(t, IsAlreadyExists(db.PutCommitComment(cc1different))) | |
713 | |
714 expected := []*RepoComments{ | |
715 &RepoComments{Repo: "r0"}, | |
716 &RepoComments{ | |
717 Repo: "r1", | |
718 TaskComments: map[string]map[string][]*TaskComment{ | |
719 "n1": { | |
720 "c1": {tc1, tc3, tc2}, | |
721 "c2": {tc4}, | |
722 }, | |
723 "n2": { | |
724 "c2": {tc5}, | |
725 }, | |
726 }, | |
727 TaskSpecComments: map[string][]*TaskSpecComment{ | |
728 "n1": {sc1, sc3, sc2}, | |
729 "n2": {sc4}, | |
730 }, | |
731 CommitComments: map[string][]*CommitComment{ | |
732 "c1": {cc1, cc3, cc2}, | |
733 "c2": {cc4}, | |
734 }, | |
735 }, | |
736 &RepoComments{ | |
737 Repo: "r2", | |
738 TaskComments: map[string]map[string][]*TaskComment{ | |
739 "n3": { | |
740 "c3": {tc6copy}, | |
741 }, | |
742 }, | |
743 TaskSpecComments: map[string][]*TaskSpecComment{ | |
744 "n3": {sc5copy}, | |
745 }, | |
746 CommitComments: map[string][]*CommitComment{ | |
747 "c3": {cc5copy}, | |
748 }, | |
749 }, | |
750 } | |
751 { | |
752 actual, err := db.GetCommentsForRepos([]string{"r0", "r1", "r2"}
, now.Add(-10000*time.Hour)) | |
753 assert.NoError(t, err) | |
754 testutils.AssertDeepEqual(t, expected, actual) | |
755 } | |
756 | |
757 // Specifying a cutoff time shouldn't drop required comments. | |
758 { | |
759 actual, err := db.GetCommentsForRepos([]string{"r1"}, now.Add(ti
me.Second)) | |
760 assert.NoError(t, err) | |
761 assert.Equal(t, 1, len(actual)) | |
762 { | |
763 tcs := actual[0].TaskComments["n1"]["c1"] | |
764 assert.True(t, len(tcs) >= 2) | |
765 offset := 0 | |
766 if !tcs[0].Timestamp.Equal(tc3.Timestamp) { | |
767 offset = 1 | |
768 } | |
769 testutils.AssertDeepEqual(t, tc3, tcs[offset]) | |
770 testutils.AssertDeepEqual(t, tc2, tcs[offset+1]) | |
771 } | |
772 { | |
773 scs := actual[0].TaskSpecComments["n1"] | |
774 assert.True(t, len(scs) >= 2) | |
775 offset := 0 | |
776 if !scs[0].Timestamp.Equal(sc3.Timestamp) { | |
777 offset = 1 | |
778 } | |
779 testutils.AssertDeepEqual(t, sc3, scs[offset]) | |
780 testutils.AssertDeepEqual(t, sc2, scs[offset+1]) | |
781 } | |
782 { | |
783 ccs := actual[0].CommitComments["c1"] | |
784 assert.True(t, len(ccs) >= 2) | |
785 offset := 0 | |
786 if !ccs[0].Timestamp.Equal(cc3.Timestamp) { | |
787 offset = 1 | |
788 } | |
789 testutils.AssertDeepEqual(t, cc3, ccs[offset]) | |
790 testutils.AssertDeepEqual(t, cc2, ccs[offset+1]) | |
791 } | |
792 } | |
793 | |
794 // Delete some comments. | |
795 assert.NoError(t, db.DeleteTaskComment(tc3)) | |
796 assert.NoError(t, db.DeleteTaskSpecComment(sc3)) | |
797 assert.NoError(t, db.DeleteCommitComment(cc3)) | |
798 // Delete should only look at the ID fields. | |
799 assert.NoError(t, db.DeleteTaskComment(tc1different)) | |
800 assert.NoError(t, db.DeleteTaskSpecComment(sc1different)) | |
801 assert.NoError(t, db.DeleteCommitComment(cc1different)) | |
802 // Delete of nonexistent task should succeed. | |
803 assert.NoError(t, db.DeleteTaskComment(makeTaskComment(99, 1, 1, 1, now.
Add(99*time.Second)))) | |
804 assert.NoError(t, db.DeleteTaskComment(makeTaskComment(99, 1, 1, 99, now
))) | |
805 assert.NoError(t, db.DeleteTaskComment(makeTaskComment(99, 1, 99, 1, now
))) | |
806 assert.NoError(t, db.DeleteTaskComment(makeTaskComment(99, 99, 1, 1, now
))) | |
807 assert.NoError(t, db.DeleteTaskSpecComment(makeTaskSpecComment(99, 1, 1,
now.Add(99*time.Second)))) | |
808 assert.NoError(t, db.DeleteTaskSpecComment(makeTaskSpecComment(99, 1, 99
, now))) | |
809 assert.NoError(t, db.DeleteTaskSpecComment(makeTaskSpecComment(99, 99, 1
, now))) | |
810 assert.NoError(t, db.DeleteCommitComment(makeCommitComment(99, 1, 1, now
.Add(99*time.Second)))) | |
811 assert.NoError(t, db.DeleteCommitComment(makeCommitComment(99, 1, 99, no
w))) | |
812 assert.NoError(t, db.DeleteCommitComment(makeCommitComment(99, 99, 1, no
w))) | |
813 | |
814 expected[1].TaskComments["n1"]["c1"] = []*TaskComment{tc2} | |
815 expected[1].TaskSpecComments["n1"] = []*TaskSpecComment{sc2} | |
816 expected[1].CommitComments["c1"] = []*CommitComment{cc2} | |
817 { | |
818 actual, err := db.GetCommentsForRepos([]string{"r0", "r1", "r2"}
, now.Add(-10000*time.Hour)) | |
819 assert.NoError(t, err) | |
820 testutils.AssertDeepEqual(t, expected, actual) | |
821 } | |
822 | |
823 // Delete all the comments. | |
824 for _, c := range []*TaskComment{tc2, tc4, tc5, tc6} { | |
825 assert.NoError(t, db.DeleteTaskComment(c)) | |
826 } | |
827 for _, c := range []*TaskSpecComment{sc2, sc4, sc5} { | |
828 assert.NoError(t, db.DeleteTaskSpecComment(c)) | |
829 } | |
830 for _, c := range []*CommitComment{cc2, cc4, cc5} { | |
831 assert.NoError(t, db.DeleteCommitComment(c)) | |
832 } | |
833 { | |
834 actual, err := db.GetCommentsForRepos([]string{"r0", "r1", "r2"}
, now.Add(-10000*time.Hour)) | |
835 assert.NoError(t, err) | |
836 assert.Equal(t, 3, len(actual)) | |
837 assert.Equal(t, "r0", actual[0].Repo) | |
838 assert.Equal(t, "r1", actual[1].Repo) | |
839 assert.Equal(t, "r2", actual[2].Repo) | |
840 for _, rc := range actual { | |
841 assert.Equal(t, 0, len(rc.TaskComments)) | |
842 assert.Equal(t, 0, len(rc.TaskSpecComments)) | |
843 assert.Equal(t, 0, len(rc.CommitComments)) | |
844 } | |
845 } | |
846 } | |
OLD | NEW |