| 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 |