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 |