| OLD | NEW |
| (Empty) |
| 1 package local_db | |
| 2 | |
| 3 import ( | |
| 4 "io/ioutil" | |
| 5 "path/filepath" | |
| 6 "sort" | |
| 7 "testing" | |
| 8 "time" | |
| 9 | |
| 10 assert "github.com/stretchr/testify/require" | |
| 11 "go.skia.org/infra/build_scheduler/go/db" | |
| 12 "go.skia.org/infra/go/testutils" | |
| 13 "go.skia.org/infra/go/util" | |
| 14 ) | |
| 15 | |
| 16 // Check that formatId and parseId are inverse operations and produce the | |
| 17 // expected result. | |
| 18 func TestFormatParseId(t *testing.T) { | |
| 19 testCases := []struct { | |
| 20 ts time.Time | |
| 21 seq uint64 | |
| 22 id string | |
| 23 }{ | |
| 24 { | |
| 25 ts: time.Date(2009, time.November, 10, 23, 45, 6, 1500,
time.UTC), | |
| 26 seq: 0, | |
| 27 id: "20091110T234506.000001500Z_0000000000000000", | |
| 28 }, | |
| 29 { | |
| 30 ts: time.Date(2001, time.February, 3, 4, 5, 6, 0, time.
FixedZone("fake", 45*60)), | |
| 31 seq: 1, | |
| 32 // Subtract 45 minutes due to zone. | |
| 33 id: "20010203T032006.000000000Z_0000000000000001", | |
| 34 }, | |
| 35 { | |
| 36 ts: time.Date(2001, time.January, 1, 1, 1, 1, 100000000
, time.UTC), | |
| 37 seq: 15, | |
| 38 id: "20010101T010101.100000000Z_000000000000000f", | |
| 39 }, | |
| 40 { | |
| 41 ts: time.Date(2001, time.January, 1, 1, 1, 1, 100000000
, time.UTC), | |
| 42 seq: 16, | |
| 43 id: "20010101T010101.100000000Z_0000000000000010", | |
| 44 }, | |
| 45 { | |
| 46 ts: time.Date(2001, time.January, 1, 1, 1, 1, 100000000
, time.UTC), | |
| 47 seq: 255, | |
| 48 id: "20010101T010101.100000000Z_00000000000000ff", | |
| 49 }, | |
| 50 { | |
| 51 ts: time.Date(2001, time.January, 1, 1, 1, 1, 100000000
, time.UTC), | |
| 52 seq: 0xFFFFFFFFFFFFFFFF, | |
| 53 id: "20010101T010101.100000000Z_ffffffffffffffff", | |
| 54 }, | |
| 55 } | |
| 56 for _, testCase := range testCases { | |
| 57 assert.Equal(t, testCase.id, formatId(testCase.ts, testCase.seq)
) | |
| 58 ts, seq, err := parseId(testCase.id) | |
| 59 assert.NoError(t, err) | |
| 60 assert.True(t, testCase.ts.Equal(ts)) | |
| 61 assert.Equal(t, testCase.seq, seq) | |
| 62 assert.Equal(t, time.UTC, ts.Location()) | |
| 63 } | |
| 64 | |
| 65 // Invalid timestamps: | |
| 66 for _, invalidId := range []string{ | |
| 67 // Missing seq num. | |
| 68 "20091110T234506.000001500Z", | |
| 69 // Two-digit year. | |
| 70 "091110T234506.000001500Z_0000000000000000", | |
| 71 // Invalid month. | |
| 72 "20010001T010101.100000000Z_000000000000000f", | |
| 73 // Missing T. | |
| 74 "20010101010101.100000000Z_000000000000000f", | |
| 75 // Missing Z. | |
| 76 "20010101T010101.100000000_000000000000000f", | |
| 77 // Empty seq num. | |
| 78 "20010101T010101.100000000Z_", | |
| 79 // Invalid char in seq num. | |
| 80 "20010101T010101.100000000Z_000000000000000g", | |
| 81 // Invalid char in seq num. | |
| 82 "20010101T010101.100000000Z_g000000000000000", | |
| 83 // Empty timestamp. | |
| 84 "_000000000000000f", | |
| 85 // Sequence num overflows. | |
| 86 "20010101T010101.100000000Z_1ffffffffffffffff", | |
| 87 } { | |
| 88 _, _, err := parseId(invalidId) | |
| 89 assert.Error(t, err, "No error for Id: %q", invalidId) | |
| 90 } | |
| 91 } | |
| 92 | |
| 93 // Check that packV1 and unpackV1 are inverse operations and produce the | |
| 94 // expected result. | |
| 95 func TestPackUnpackV1(t *testing.T) { | |
| 96 testCases := []struct { | |
| 97 ts time.Time | |
| 98 data []byte | |
| 99 packed []byte | |
| 100 }{ | |
| 101 { | |
| 102 ts: time.Unix(0, 0x1174f263b54399dc), | |
| 103 data: []byte{0xab, 0xcd, 0xef, 0x01, 0x23}, | |
| 104 packed: []byte{0x01, 0x11, 0x74, 0xf2, 0x63, 0xb5, 0x43,
0x99, 0xdc, 0xab, 0xcd, 0xef, 0x01, 0x23}, | |
| 105 }, | |
| 106 { | |
| 107 ts: time.Date(2262, time.April, 11, 23, 47, 16, 8547
75807, time.UTC), | |
| 108 data: []byte("Hi Mom!"), | |
| 109 packed: append([]byte{0x01, 0x7f, 0xff, 0xff, 0xff, 0xff
, 0xff, 0xff, 0xff}, "Hi Mom!"...), | |
| 110 }, | |
| 111 { | |
| 112 ts: time.Unix(0, 0), | |
| 113 data: []byte{}, | |
| 114 packed: []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00}, | |
| 115 }, | |
| 116 } | |
| 117 for _, testCase := range testCases { | |
| 118 assert.Equal(t, testCase.packed, packV1(testCase.ts, testCase.da
ta)) | |
| 119 ts, data, err := unpackV1(testCase.packed) | |
| 120 assert.NoError(t, err) | |
| 121 assert.True(t, testCase.ts.Equal(ts)) | |
| 122 assert.Equal(t, testCase.data, data) | |
| 123 assert.Equal(t, time.UTC, ts.Location()) | |
| 124 } | |
| 125 | |
| 126 for _, invalid := range [][]byte{ | |
| 127 []byte{}, | |
| 128 []byte{0x00}, | |
| 129 []byte{0x01, 0x00, 0x00}, | |
| 130 []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, | |
| 131 } { | |
| 132 _, _, err := unpackV1(invalid) | |
| 133 assert.Error(t, err) | |
| 134 } | |
| 135 } | |
| 136 | |
| 137 // Create a localDB for testing. Call defer util.RemoveAll() on the second | |
| 138 // return value. | |
| 139 func makeDB(t *testing.T, name string) (db.DB, string) { | |
| 140 //testutils.SkipIfShort(t) | |
| 141 tmpdir, err := ioutil.TempDir("", name) | |
| 142 assert.NoError(t, err) | |
| 143 d, err := NewDB(name, filepath.Join(tmpdir, "task.db")) | |
| 144 assert.NoError(t, err) | |
| 145 return d, tmpdir | |
| 146 } | |
| 147 | |
| 148 // Test that AssignId returns an error if Id is set. | |
| 149 func TestAssignIdAlreadyAssigned(t *testing.T) { | |
| 150 d, tmpdir := makeDB(t, "TestAssignIdsFromCreatedTs") | |
| 151 defer util.RemoveAll(tmpdir) | |
| 152 defer testutils.AssertCloses(t, d) | |
| 153 | |
| 154 task := &db.Task{} | |
| 155 assert.NoError(t, d.AssignId(task)) | |
| 156 assert.Error(t, d.AssignId(task)) | |
| 157 } | |
| 158 | |
| 159 // Test that AssignId uses created timestamp when set, and generates unique IDs | |
| 160 // for the same timestamp. | |
| 161 func TestAssignIdsFromCreatedTs(t *testing.T) { | |
| 162 d, tmpdir := makeDB(t, "TestAssignIdsFromCreatedTs") | |
| 163 defer util.RemoveAll(tmpdir) | |
| 164 defer testutils.AssertCloses(t, d) | |
| 165 | |
| 166 tasks := []*db.Task{} | |
| 167 addTask := func(ts time.Time) { | |
| 168 task := &db.Task{ | |
| 169 Created: ts, | |
| 170 } | |
| 171 assert.NoError(t, d.AssignId(task)) | |
| 172 tasks = append(tasks, task) | |
| 173 } | |
| 174 | |
| 175 // Add tasks with various creation timestamps. | |
| 176 addTask(time.Date(2008, time.August, 8, 8, 8, 8, 8, time.UTC)) | |
| 177 addTask(time.Date(1776, time.July, 4, 13, 0, 0, 0, time.UTC)) | |
| 178 addTask(time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)) | |
| 179 addTask(time.Date(2016, time.December, 31, 23, 59, 59, 999999999, time.U
TC)) | |
| 180 // Repeated timestamps. | |
| 181 addTask(time.Date(2008, time.August, 8, 8, 8, 8, 8, time.UTC)) | |
| 182 addTask(time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)) | |
| 183 for i := 0; i < 256; i++ { | |
| 184 addTask(time.Date(2008, time.August, 8, 8, 8, 8, 8, time.UTC)) | |
| 185 } | |
| 186 | |
| 187 // Collect IDs. Assert Id is set. | |
| 188 ids := make([]string, 0, len(tasks)) | |
| 189 for _, task := range tasks { | |
| 190 assert.NotEqual(t, "", task.Id) | |
| 191 ids = append(ids, task.Id) | |
| 192 } | |
| 193 | |
| 194 // Stable-sort tasks. | |
| 195 sort.Stable(db.TaskSlice(tasks)) | |
| 196 | |
| 197 // Sort IDs. | |
| 198 sort.Strings(ids) | |
| 199 | |
| 200 // Validate that sorted IDs match sorted Tasks. Check that there are no | |
| 201 // duplicate IDs. Check that ID timestamp matches created timestamp. | |
| 202 prevId := "" | |
| 203 for i := 0; i < len(tasks); i++ { | |
| 204 assert.Equal(t, ids[i], tasks[i].Id) | |
| 205 assert.NotEqual(t, prevId, ids[i]) | |
| 206 ts, _, err := parseId(ids[i]) | |
| 207 assert.NoError(t, err) | |
| 208 assert.True(t, ts.Equal(tasks[i].Created)) | |
| 209 prevId = ids[i] | |
| 210 } | |
| 211 } | |
| 212 | |
| 213 // Test that AssignId can generate ids when created timestamp is not set, and | |
| 214 // generates unique IDs for PutTasks. | |
| 215 func TestAssignIdsFromCurrentTime(t *testing.T) { | |
| 216 d, tmpdir := makeDB(t, "TestAssignIdsFromCurrentTime") | |
| 217 defer util.RemoveAll(tmpdir) | |
| 218 defer testutils.AssertCloses(t, d) | |
| 219 | |
| 220 tasks := []*db.Task{} | |
| 221 for i := 0; i < 260; i++ { | |
| 222 tasks = append(tasks, &db.Task{}) | |
| 223 } | |
| 224 | |
| 225 begin := time.Now() | |
| 226 | |
| 227 // Test AssignId. | |
| 228 assert.NoError(t, d.AssignId(tasks[5])) | |
| 229 assert.NoError(t, d.AssignId(tasks[6])) | |
| 230 id5, id6 := tasks[5].Id, tasks[6].Id | |
| 231 | |
| 232 // Created time is required. | |
| 233 for _, task := range tasks { | |
| 234 task.Created = time.Now() | |
| 235 } | |
| 236 | |
| 237 // Test PutTasks. | |
| 238 assert.NoError(t, d.PutTasks(tasks)) | |
| 239 | |
| 240 end := time.Now() | |
| 241 | |
| 242 // Check that PutTasks did not change existing Ids. | |
| 243 assert.Equal(t, id5, tasks[5].Id) | |
| 244 assert.Equal(t, id6, tasks[6].Id) | |
| 245 | |
| 246 // Order tasks by time of ID assignment. | |
| 247 first2 := []*db.Task{tasks[5], tasks[6]} | |
| 248 copy(tasks[2:7], tasks[0:5]) | |
| 249 copy(tasks[0:2], first2) | |
| 250 | |
| 251 // Collect IDs. Assert Id is set. | |
| 252 ids := make([]string, 0, len(tasks)) | |
| 253 for _, task := range tasks { | |
| 254 assert.NotEqual(t, "", task.Id) | |
| 255 ids = append(ids, task.Id) | |
| 256 } | |
| 257 | |
| 258 // Sort IDs. | |
| 259 sort.Strings(ids) | |
| 260 | |
| 261 // Validate that sorted IDs match Tasks by insertion order. Check that t
here | |
| 262 // are no duplicate IDs. Check that begin <= ID timestamp <= end. | |
| 263 prevId := "" | |
| 264 for i := 0; i < len(tasks); i++ { | |
| 265 assert.Equal(t, ids[i], tasks[i].Id) | |
| 266 assert.NotEqual(t, prevId, ids[i]) | |
| 267 ts, _, err := parseId(ids[i]) | |
| 268 assert.NoError(t, err) | |
| 269 assert.True(t, begin.Before(ts) || begin.Equal(ts)) | |
| 270 assert.True(t, ts.Before(end) || ts.Equal(end)) | |
| 271 prevId = ids[i] | |
| 272 } | |
| 273 } | |
| 274 | |
| 275 // Test that PutTask returns an error when AssignId time is too far before (or | |
| 276 // after) the value subsequently assigned to Task.Created. | |
| 277 func TestPutTaskValidateCreatedTime(t *testing.T) { | |
| 278 d, tmpdir := makeDB(t, "TestPutTaskValidateCreatedTime") | |
| 279 defer util.RemoveAll(tmpdir) | |
| 280 defer testutils.AssertCloses(t, d) | |
| 281 | |
| 282 task := &db.Task{} | |
| 283 beforeAssignId := time.Now().Add(-time.Nanosecond) | |
| 284 assert.NoError(t, d.AssignId(task)) | |
| 285 afterAssignId := time.Now().Add(time.Nanosecond) | |
| 286 | |
| 287 // Test "not set". | |
| 288 { | |
| 289 err := d.PutTask(task) | |
| 290 assert.Error(t, err) | |
| 291 assert.Contains(t, err.Error(), "Created not set.") | |
| 292 } | |
| 293 | |
| 294 // Test "too late". | |
| 295 { | |
| 296 task.Created = afterAssignId.Add(MAX_CREATED_TIME_SKEW) | |
| 297 err := d.PutTask(task) | |
| 298 assert.Error(t, err) | |
| 299 assert.Contains(t, err.Error(), "Created too late.") | |
| 300 } | |
| 301 | |
| 302 // Test "too early". | |
| 303 { | |
| 304 task.Created = beforeAssignId | |
| 305 err := d.PutTask(task) | |
| 306 assert.Error(t, err) | |
| 307 assert.Contains(t, err.Error(), "Created too early.") | |
| 308 | |
| 309 // Verify not in DB. | |
| 310 noTask, err := d.GetTaskById(task.Id) | |
| 311 assert.NoError(t, err) | |
| 312 assert.Nil(t, noTask) | |
| 313 } | |
| 314 | |
| 315 // Test in range. | |
| 316 { | |
| 317 task.Created = beforeAssignId.Add(MAX_CREATED_TIME_SKEW) | |
| 318 err := d.PutTask(task) | |
| 319 assert.NoError(t, err) | |
| 320 | |
| 321 // Verify added to DB. | |
| 322 taskCopy, err := d.GetTaskById(task.Id) | |
| 323 assert.NoError(t, err) | |
| 324 testutils.AssertDeepEqual(t, task, taskCopy) | |
| 325 } | |
| 326 | |
| 327 // We can even change the Created time if we want. (Not necessarily supp
orted | |
| 328 // by all DB implementations.) | |
| 329 { | |
| 330 task.Created = afterAssignId | |
| 331 err := d.PutTask(task) | |
| 332 assert.NoError(t, err) | |
| 333 | |
| 334 taskCopy, err := d.GetTaskById(task.Id) | |
| 335 assert.NoError(t, err) | |
| 336 testutils.AssertDeepEqual(t, task, taskCopy) | |
| 337 } | |
| 338 | |
| 339 // But we can't change it to be out of range. | |
| 340 { | |
| 341 prevCreated := task.Created | |
| 342 task.Created = beforeAssignId | |
| 343 err := d.PutTask(task) | |
| 344 assert.Error(t, err) | |
| 345 assert.Contains(t, err.Error(), "Created too early.") | |
| 346 | |
| 347 taskCopy, err := d.GetTaskById(task.Id) | |
| 348 assert.NoError(t, err) | |
| 349 assert.True(t, prevCreated.Equal(taskCopy.Created)) | |
| 350 } | |
| 351 } | |
| 352 | |
| 353 // Test that PutTask/s does not modify the passed-in Tasks when there is an | |
| 354 // error. | |
| 355 func TestPutTaskLeavesTasksUnchanged(t *testing.T) { | |
| 356 d, tmpdir := makeDB(t, "TestPutTaskLeavesTasksUnchanged") | |
| 357 defer util.RemoveAll(tmpdir) | |
| 358 defer testutils.AssertCloses(t, d) | |
| 359 | |
| 360 begin := time.Now().Add(-time.Nanosecond) | |
| 361 | |
| 362 // Create and insert a task that will cause ErrConcurrentUpdate. | |
| 363 task1 := &db.Task{ | |
| 364 Created: time.Now(), | |
| 365 } | |
| 366 assert.NoError(t, d.PutTask(task1)) | |
| 367 | |
| 368 // Retrieve a copy, modify original. | |
| 369 task1Cached, err := d.GetTaskById(task1.Id) | |
| 370 assert.NoError(t, err) | |
| 371 task1.Status = db.TASK_STATUS_RUNNING | |
| 372 assert.NoError(t, d.PutTask(task1)) | |
| 373 task1InDb := task1.Copy() | |
| 374 | |
| 375 // Create and insert a task to check PutTasks doesn't change DbModified. | |
| 376 task2 := &db.Task{ | |
| 377 Created: time.Now(), | |
| 378 } | |
| 379 assert.NoError(t, d.PutTask(task2)) | |
| 380 task2InDb := task2.Copy() | |
| 381 task2.Status = db.TASK_STATUS_MISHAP | |
| 382 | |
| 383 // Create a task with an Id already set. | |
| 384 task3 := &db.Task{} | |
| 385 assert.NoError(t, d.AssignId(task3)) | |
| 386 task3.Created = time.Now() | |
| 387 | |
| 388 // Create a task without an Id set. | |
| 389 task4 := &db.Task{ | |
| 390 Created: time.Now(), | |
| 391 } | |
| 392 | |
| 393 // Make an update to task1Cached. | |
| 394 task1Cached.Commits = []string{"a", "b"} | |
| 395 | |
| 396 // Copy to compare later. | |
| 397 expectedTasks := []*db.Task{task1Cached.Copy(), task2.Copy(), task3.Copy
(), task4.Copy()} | |
| 398 | |
| 399 // Attempt to insert; put task1Cached last so that the error comes last. | |
| 400 err = d.PutTasks([]*db.Task{task2, task3, task4, task1Cached}) | |
| 401 assert.True(t, db.IsConcurrentUpdate(err)) | |
| 402 testutils.AssertDeepEqual(t, expectedTasks, []*db.Task{task1Cached, task
2, task3, task4}) | |
| 403 | |
| 404 // Check that nothing was updated in the DB. | |
| 405 tasksInDb, err := d.GetTasksFromDateRange(begin, time.Now()) | |
| 406 assert.NoError(t, err) | |
| 407 assert.Equal(t, 2, len(tasksInDb)) | |
| 408 for _, task := range tasksInDb { | |
| 409 switch task.Id { | |
| 410 case task1.Id: | |
| 411 testutils.AssertDeepEqual(t, task1InDb, task) | |
| 412 case task2.Id: | |
| 413 testutils.AssertDeepEqual(t, task2InDb, task) | |
| 414 default: | |
| 415 assert.Fail(t, "Unexpected task in DB: %v", task) | |
| 416 } | |
| 417 } | |
| 418 } | |
| 419 | |
| 420 func TestLocalDB(t *testing.T) { | |
| 421 d, tmpdir := makeDB(t, "TestLocalDB") | |
| 422 defer util.RemoveAll(tmpdir) | |
| 423 db.TestDB(t, d) | |
| 424 } | |
| 425 | |
| 426 func TestLocalDBTooManyUsers(t *testing.T) { | |
| 427 d, tmpdir := makeDB(t, "TestLocalDBTooManyUsers") | |
| 428 defer util.RemoveAll(tmpdir) | |
| 429 db.TestTooManyUsers(t, d) | |
| 430 } | |
| 431 | |
| 432 func TestLocalDBConcurrentUpdate(t *testing.T) { | |
| 433 d, tmpdir := makeDB(t, "TestLocalDBConcurrentUpdate") | |
| 434 defer util.RemoveAll(tmpdir) | |
| 435 db.TestConcurrentUpdate(t, d) | |
| 436 } | |
| 437 | |
| 438 func TestLocalDBUpdateWithRetries(t *testing.T) { | |
| 439 d, tmpdir := makeDB(t, "TestLocalDBUpdateWithRetries") | |
| 440 defer util.RemoveAll(tmpdir) | |
| 441 db.TestUpdateWithRetries(t, d) | |
| 442 } | |
| OLD | NEW |