Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(327)

Side by Side Diff: build_scheduler/go/task_scheduler/task_scheduler_test.go

Issue 2296763008: [task scheduler] Move files from build_scheduler/ to task_scheduler/ (Closed) Base URL: https://skia.googlesource.com/buildbot@master
Patch Set: Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 package task_scheduler
2
3 import (
4 "encoding/json"
5 "fmt"
6 "io/ioutil"
7 "math"
8 "os"
9 "path"
10 "path/filepath"
11 "runtime"
12 "sort"
13 "strings"
14 "testing"
15 "time"
16
17 swarming_api "github.com/luci/luci-go/common/api/swarming/swarming/v1"
18 assert "github.com/stretchr/testify/require"
19 "go.skia.org/infra/build_scheduler/go/db"
20 "go.skia.org/infra/go/buildbot"
21 "go.skia.org/infra/go/exec"
22 "go.skia.org/infra/go/gitinfo"
23 "go.skia.org/infra/go/gitrepo"
24 "go.skia.org/infra/go/isolate"
25 "go.skia.org/infra/go/swarming"
26 "go.skia.org/infra/go/testutils"
27 "go.skia.org/infra/go/util"
28 )
29
30 func makeTask(name, repo, revision string) *db.Task {
31 return &db.Task{
32 Commits: []string{revision},
33 Created: time.Now(),
34 Name: name,
35 Repo: repo,
36 Revision: revision,
37 }
38 }
39
40 func makeSwarmingRpcsTaskRequestMetadata(t *testing.T, task *db.Task) *swarming_ api.SwarmingRpcsTaskRequestMetadata {
41 tag := func(k, v string) string {
42 return fmt.Sprintf("%s:%s", k, v)
43 }
44 ts := func(t time.Time) string {
45 return t.Format(swarming.TIMESTAMP_FORMAT)
46 }
47 abandoned := ""
48 state := db.SWARMING_STATE_PENDING
49 failed := false
50 switch task.Status {
51 case db.TASK_STATUS_MISHAP:
52 state = db.SWARMING_STATE_BOT_DIED
53 abandoned = ts(task.Finished)
54 case db.TASK_STATUS_RUNNING:
55 state = db.SWARMING_STATE_RUNNING
56 case db.TASK_STATUS_FAILURE:
57 state = db.SWARMING_STATE_COMPLETED
58 failed = true
59 case db.TASK_STATUS_SUCCESS:
60 state = db.SWARMING_STATE_COMPLETED
61 case db.TASK_STATUS_PENDING:
62 // noop
63 default:
64 assert.FailNow(t, "Unknown task status: %s", task.Status)
65 }
66 return &swarming_api.SwarmingRpcsTaskRequestMetadata{
67 Request: &swarming_api.SwarmingRpcsTaskRequest{},
68 TaskId: task.SwarmingTaskId,
69 TaskResult: &swarming_api.SwarmingRpcsTaskResult{
70 AbandonedTs: abandoned,
71 CreatedTs: ts(task.Created),
72 CompletedTs: ts(task.Finished),
73 Failure: failed,
74 OutputsRef: &swarming_api.SwarmingRpcsFilesRef{
75 Isolated: "???",
76 },
77 StartedTs: ts(task.Started),
78 State: state,
79 Tags: []string{
80 tag(db.SWARMING_TAG_ID, task.Id),
81 tag(db.SWARMING_TAG_NAME, task.Name),
82 tag(db.SWARMING_TAG_REPO, task.Repo),
83 tag(db.SWARMING_TAG_REVISION, task.Revision),
84 },
85 TaskId: task.SwarmingTaskId,
86 },
87 }
88 }
89
90 func TestFindTaskCandidates(t *testing.T) {
91 testutils.SkipIfShort(t)
92
93 // Setup.
94 tr := util.NewTempRepo()
95 defer tr.Cleanup()
96 d := db.NewInMemoryDB()
97 cache, err := db.NewTaskCache(d, time.Hour)
98 assert.NoError(t, err)
99
100 // The test repo has two commits. The first commit adds a tasks.cfg file
101 // with two task specs: a build task and a test task, the test task
102 // depending on the build task. The second commit adds a perf task spec,
103 // which also depends on the build task. Therefore, there are five total
104 // possible tasks we could run:
105 //
106 // Build@c1, Test@c1, Build@c2, Test@c2, Perf@c2
107 //
108 c1 := "c06ac6093d3029dffe997e9d85e8e61fee5f87b9"
109 c2 := "0f87799ac791b8d8573e93694d05b05a65e09668"
110 buildTask := "Build-Ubuntu-GCC-Arm7-Release-Android"
111 testTask := "Test-Android-GCC-Nexus7-GPU-Tegra3-Arm7-Release"
112 perfTask := "Perf-Android-GCC-Nexus7-GPU-Tegra3-Arm7-Release"
113 repo := "skia.git"
114 commits := map[string][]string{
115 repo: []string{c1, c2},
116 }
117
118 assert.NoError(t, err)
119 isolateClient, err := isolate.NewClient(tr.Dir)
120 assert.NoError(t, err)
121 isolateClient.ServerUrl = isolate.FAKE_SERVER_URL
122 swarmingClient := swarming.NewTestClient()
123 s, err := NewTaskScheduler(d, cache, time.Duration(math.MaxInt64), tr.Di r, []string{"skia.git"}, isolateClient, swarmingClient)
124 assert.NoError(t, err)
125
126 // Check the initial set of task candidates. The two Build tasks
127 // should be the only ones available.
128 c, err := s.findTaskCandidates(commits)
129 assert.NoError(t, err)
130 assert.Equal(t, 1, len(c))
131 key := fmt.Sprintf("%s|%s", repo, buildTask)
132 assert.Equal(t, 2, len(c[key]))
133 for _, candidate := range c[key] {
134 assert.Equal(t, candidate.Name, buildTask)
135 }
136
137 // Insert a the Build task at c1 (1 dependent) into the database,
138 // transition through various states.
139 var t1 *db.Task
140 for _, candidates := range c { // Order not guaranteed; find the right c andidate.
141 for _, candidate := range candidates {
142 if candidate.Revision == c1 {
143 t1 = makeTask(candidate.Name, candidate.Repo, ca ndidate.Revision)
144 break
145 }
146 }
147 }
148 assert.NotNil(t, t1)
149
150 // We shouldn't duplicate pending or running tasks.
151 for _, status := range []db.TaskStatus{db.TASK_STATUS_PENDING, db.TASK_S TATUS_RUNNING} {
152 t1.Status = status
153 assert.NoError(t, d.PutTask(t1))
154 assert.NoError(t, cache.Update())
155
156 c, err = s.findTaskCandidates(commits)
157 assert.NoError(t, err)
158 assert.Equal(t, 1, len(c))
159 for _, candidates := range c {
160 for _, candidate := range candidates {
161 assert.Equal(t, candidate.Name, buildTask)
162 assert.Equal(t, c2, candidate.Revision)
163 }
164 }
165 }
166
167 // The task failed. Ensure that its dependents are not candidates, but
168 // the task itself is back in the list of candidates, in case we want
169 // to retry.
170 t1.Status = db.TASK_STATUS_FAILURE
171 assert.NoError(t, d.PutTask(t1))
172 assert.NoError(t, cache.Update())
173
174 c, err = s.findTaskCandidates(commits)
175 assert.NoError(t, err)
176 assert.Equal(t, 1, len(c))
177 for _, candidates := range c {
178 assert.Equal(t, 2, len(candidates))
179 for _, candidate := range candidates {
180 assert.Equal(t, candidate.Name, buildTask)
181 }
182 }
183
184 // The task succeeded. Ensure that its dependents are candidates and
185 // the task itself is not.
186 t1.Status = db.TASK_STATUS_SUCCESS
187 t1.IsolatedOutput = "fake isolated hash"
188 assert.NoError(t, d.PutTask(t1))
189 assert.NoError(t, cache.Update())
190
191 c, err = s.findTaskCandidates(commits)
192 assert.NoError(t, err)
193 assert.Equal(t, 2, len(c))
194 for _, candidates := range c {
195 for _, candidate := range candidates {
196 assert.False(t, t1.Name == candidate.Name && t1.Revision == candidate.Revision)
197 }
198 }
199
200 // Create the other Build task.
201 var t2 *db.Task
202 for _, candidates := range c {
203 for _, candidate := range candidates {
204 if candidate.Revision == c2 && strings.HasPrefix(candida te.Name, "Build-") {
205 t2 = makeTask(candidate.Name, candidate.Repo, ca ndidate.Revision)
206 break
207 }
208 }
209 }
210 assert.NotNil(t, t2)
211 t2.Status = db.TASK_STATUS_SUCCESS
212 t2.IsolatedOutput = "fake isolated hash"
213 assert.NoError(t, d.PutTask(t2))
214 assert.NoError(t, cache.Update())
215
216 // All test and perf tasks are now candidates, no build tasks.
217 c, err = s.findTaskCandidates(commits)
218 assert.NoError(t, err)
219 assert.Equal(t, 2, len(c))
220 assert.Equal(t, 2, len(c[fmt.Sprintf("%s|%s", repo, testTask)]))
221 assert.Equal(t, 1, len(c[fmt.Sprintf("%s|%s", repo, perfTask)]))
222 for _, candidates := range c {
223 for _, candidate := range candidates {
224 assert.NotEqual(t, candidate.Name, buildTask)
225 }
226 }
227 }
228
229 func TestTestedness(t *testing.T) {
230 tc := []struct {
231 in int
232 out float64
233 }{
234 {
235 in: -1,
236 out: -1.0,
237 },
238 {
239 in: 0,
240 out: 0.0,
241 },
242 {
243 in: 1,
244 out: 1.0,
245 },
246 {
247 in: 2,
248 out: 1.0 + 1.0/2.0,
249 },
250 {
251 in: 3,
252 out: 1.0 + float64(2.0)/float64(3.0),
253 },
254 {
255 in: 4,
256 out: 1.0 + 3.0/4.0,
257 },
258 {
259 in: 4096,
260 out: 1.0 + float64(4095)/float64(4096),
261 },
262 }
263 for i, c := range tc {
264 assert.Equal(t, c.out, testedness(c.in), fmt.Sprintf("test case #%d", i))
265 }
266 }
267
268 func TestTestednessIncrease(t *testing.T) {
269 tc := []struct {
270 a int
271 b int
272 out float64
273 }{
274 // Invalid cases.
275 {
276 a: -1,
277 b: 10,
278 out: -1.0,
279 },
280 {
281 a: 10,
282 b: -1,
283 out: -1.0,
284 },
285 {
286 a: 0,
287 b: -1,
288 out: -1.0,
289 },
290 {
291 a: 0,
292 b: 0,
293 out: -1.0,
294 },
295 // Invalid because if we're re-running at already-tested commits
296 // then we should have a blamelist which is at most the size of
297 // the blamelist of the previous task. We naturally get negative
298 // testedness increase in these cases.
299 {
300 a: 2,
301 b: 1,
302 out: -0.5,
303 },
304 // Testing only new commits.
305 {
306 a: 1,
307 b: 0,
308 out: 1.0 + 1.0,
309 },
310 {
311 a: 2,
312 b: 0,
313 out: 2.0 + (1.0 + 1.0/2.0),
314 },
315 {
316 a: 3,
317 b: 0,
318 out: 3.0 + (1.0 + float64(2.0)/float64(3.0)),
319 },
320 {
321 a: 4096,
322 b: 0,
323 out: 4096.0 + (1.0 + float64(4095.0)/float64(4096.0)),
324 },
325 // Retries.
326 {
327 a: 1,
328 b: 1,
329 out: 0.0,
330 },
331 {
332 a: 2,
333 b: 2,
334 out: 0.0,
335 },
336 {
337 a: 3,
338 b: 3,
339 out: 0.0,
340 },
341 {
342 a: 4096,
343 b: 4096,
344 out: 0.0,
345 },
346 // Bisect/backfills.
347 {
348 a: 1,
349 b: 2,
350 out: 0.5, // (1 + 1) - (1 + 1/2)
351 },
352 {
353 a: 1,
354 b: 3,
355 out: float64(2.5) - (1.0 + float64(2.0)/float64(3.0)),
356 },
357 {
358 a: 5,
359 b: 10,
360 out: 2.0*(1.0+float64(4.0)/float64(5.0)) - (1.0 + float6 4(9.0)/float64(10.0)),
361 },
362 }
363 for i, c := range tc {
364 assert.Equal(t, c.out, testednessIncrease(c.a, c.b), fmt.Sprintf ("test case #%d", i))
365 }
366 }
367
368 func TestComputeBlamelist(t *testing.T) {
369 testutils.SkipIfShort(t)
370
371 // Setup.
372 _, filename, _, _ := runtime.Caller(0)
373 // Use the test repo from the buildbot package, since it's already set
374 // up for this type of test.
375 zipfile := filepath.Join(filepath.Dir(filename), "..", "..", "..", "go", "buildbot", "testdata", "testrepo.zip")
376 tr := util.NewTempRepoFrom(zipfile)
377 defer tr.Cleanup()
378 d := db.NewInMemoryDB()
379 cache, err := db.NewTaskCache(d, time.Hour)
380 assert.NoError(t, err)
381
382 // The test repo is laid out like this:
383 //
384 // * 06eb2a58139d3ff764f10232d5c8f9362d55e20f I (HEAD, master, Task #4 )
385 // * ecb424466a4f3b040586a062c15ed58356f6590e F (Task #3)
386 // |\
387 // | * d30286d2254716d396073c177a754f9e152bbb52 H
388 // | * 8d2d1247ef5d2b8a8d3394543df6c12a85881296 G (Task #2)
389 // * | 67635e7015d74b06c00154f7061987f426349d9f E
390 // * | 6d4811eddfa637fac0852c3a0801b773be1f260d D (Task #1)
391 // * | d74dfd42a48325ab2f3d4a97278fc283036e0ea4 C (Task #6)
392 // |/
393 // * 4b822ebb7cedd90acbac6a45b897438746973a87 B (Task #0)
394 // * 051955c355eb742550ddde4eccc3e90b6dc5b887 A
395 //
396 hashes := map[rune]string{
397 'A': "051955c355eb742550ddde4eccc3e90b6dc5b887",
398 'B': "4b822ebb7cedd90acbac6a45b897438746973a87",
399 'C': "d74dfd42a48325ab2f3d4a97278fc283036e0ea4",
400 'D': "6d4811eddfa637fac0852c3a0801b773be1f260d",
401 'E': "67635e7015d74b06c00154f7061987f426349d9f",
402 'F': "ecb424466a4f3b040586a062c15ed58356f6590e",
403 'G': "8d2d1247ef5d2b8a8d3394543df6c12a85881296",
404 'H': "d30286d2254716d396073c177a754f9e152bbb52",
405 'I': "06eb2a58139d3ff764f10232d5c8f9362d55e20f",
406 }
407
408 // Test cases. Each test case builds on the previous cases.
409 testCases := []struct {
410 Revision string
411 Expected []string
412 StoleFromIdx int
413 }{
414 // 0. The first task.
415 {
416 Revision: hashes['B'],
417 Expected: []string{hashes['B']}, // Task #0 is limit ed to a single commit.
418 StoleFromIdx: -1,
419 },
420 // 1. On a linear set of commits, with at least one previous tas k.
421 {
422 Revision: hashes['D'],
423 Expected: []string{hashes['D'], hashes['C']},
424 StoleFromIdx: -1,
425 },
426 // 2. The first task on a new branch.
427 {
428 Revision: hashes['G'],
429 Expected: []string{hashes['G']},
430 StoleFromIdx: -1,
431 },
432 // 3. After a merge.
433 {
434 Revision: hashes['F'],
435 Expected: []string{hashes['E'], hashes['H'], hashes[ 'F']},
436 StoleFromIdx: -1,
437 },
438 // 4. One last "normal" task.
439 {
440 Revision: hashes['I'],
441 Expected: []string{hashes['I']},
442 StoleFromIdx: -1,
443 },
444 // 5. No Revision.
445 {
446 Revision: "",
447 Expected: []string{},
448 StoleFromIdx: -1,
449 },
450 // 6. Steal commits from a previously-ingested task.
451 {
452 Revision: hashes['C'],
453 Expected: []string{hashes['C']},
454 StoleFromIdx: 1,
455 },
456 }
457 name := "Test-Ubuntu12-ShuttleA-GTX660-x86-Release"
458 repoName := "skia.git"
459 repo, err := gitrepo.NewRepo(repoName, path.Join(tr.Dir, repoName))
460 assert.NoError(t, err)
461 ids := make([]string, len(testCases))
462 commitsBuf := make([]*gitrepo.Commit, 0, buildbot.MAX_BLAMELIST_COMMITS)
463 for i, tc := range testCases {
464 // Ensure that we get the expected blamelist.
465 commits, stoleFrom, err := ComputeBlamelist(cache, repo, name, r epoName, tc.Revision, commitsBuf)
466 if tc.Revision == "" {
467 assert.Error(t, err)
468 continue
469 } else {
470 assert.NoError(t, err)
471 }
472 sort.Strings(commits)
473 testutils.AssertDeepEqual(t, tc.Expected, commits)
474 if tc.StoleFromIdx >= 0 {
475 assert.NotNil(t, stoleFrom)
476 assert.Equal(t, ids[tc.StoleFromIdx], stoleFrom.Id)
477 } else {
478 assert.Nil(t, stoleFrom)
479 }
480
481 // Insert the task into the DB.
482 c := &taskCandidate{
483 Name: name,
484 Repo: repoName,
485 Revision: tc.Revision,
486 }
487 task := c.MakeTask()
488 task.Commits = commits
489 task.Created = time.Now()
490 if stoleFrom != nil {
491 // Re-insert the stoleFrom task without the commits
492 // which were stolen from it.
493 stoleFromCommits := make([]string, 0, len(stoleFrom.Comm its)-len(commits))
494 for _, commit := range stoleFrom.Commits {
495 if !util.In(commit, task.Commits) {
496 stoleFromCommits = append(stoleFromCommi ts, commit)
497 }
498 }
499 stoleFrom.Commits = stoleFromCommits
500 assert.NoError(t, d.PutTasks([]*db.Task{task, stoleFrom} ))
501 } else {
502 assert.NoError(t, d.PutTask(task))
503 }
504 ids[i] = task.Id
505 assert.NoError(t, cache.Update())
506 }
507
508 // Extra: ensure that task #6 really stole the commit from #1.
509 task, err := cache.GetTask(ids[1])
510 assert.NoError(t, err)
511 assert.False(t, util.In(hashes['C'], task.Commits), fmt.Sprintf("Expecte d not to find %s in %v", hashes['C'], task.Commits))
512 }
513
514 func TestTimeDecay24Hr(t *testing.T) {
515 tc := []struct {
516 decayAmt24Hr float64
517 elapsed time.Duration
518 out float64
519 }{
520 {
521 decayAmt24Hr: 1.0,
522 elapsed: 10 * time.Hour,
523 out: 1.0,
524 },
525 {
526 decayAmt24Hr: 0.5,
527 elapsed: 0 * time.Hour,
528 out: 1.0,
529 },
530 {
531 decayAmt24Hr: 0.5,
532 elapsed: 24 * time.Hour,
533 out: 0.5,
534 },
535 {
536 decayAmt24Hr: 0.5,
537 elapsed: 12 * time.Hour,
538 out: 0.75,
539 },
540 {
541 decayAmt24Hr: 0.5,
542 elapsed: 36 * time.Hour,
543 out: 0.25,
544 },
545 {
546 decayAmt24Hr: 0.5,
547 elapsed: 48 * time.Hour,
548 out: 0.0,
549 },
550 {
551 decayAmt24Hr: 0.5,
552 elapsed: 72 * time.Hour,
553 out: 0.0,
554 },
555 }
556 for i, c := range tc {
557 assert.Equal(t, c.out, timeDecay24Hr(c.decayAmt24Hr, c.elapsed), fmt.Sprintf("test case #%d", i))
558 }
559 }
560
561 func TestRegenerateTaskQueue(t *testing.T) {
562 testutils.SkipIfShort(t)
563
564 // Setup.
565 tr := util.NewTempRepo()
566 defer tr.Cleanup()
567 d := db.NewInMemoryDB()
568 cache, err := db.NewTaskCache(d, time.Hour)
569 assert.NoError(t, err)
570
571 // The test repo has two commits. The first commit adds a tasks.cfg file
572 // with two task specs: a build task and a test task, the test task
573 // depending on the build task. The second commit adds a perf task spec,
574 // which also depends on the build task. Therefore, there are five total
575 // possible tasks we could run:
576 //
577 // Build@c1, Test@c1, Build@c2, Test@c2, Perf@c2
578 //
579 c1 := "c06ac6093d3029dffe997e9d85e8e61fee5f87b9"
580 c2 := "0f87799ac791b8d8573e93694d05b05a65e09668"
581 buildTask := "Build-Ubuntu-GCC-Arm7-Release-Android"
582 testTask := "Test-Android-GCC-Nexus7-GPU-Tegra3-Arm7-Release"
583 perfTask := "Perf-Android-GCC-Nexus7-GPU-Tegra3-Arm7-Release"
584 repoName := "skia.git"
585
586 assert.NoError(t, err)
587 isolateClient, err := isolate.NewClient(tr.Dir)
588 assert.NoError(t, err)
589 isolateClient.ServerUrl = isolate.FAKE_SERVER_URL
590 swarmingClient := swarming.NewTestClient()
591 s, err := NewTaskScheduler(d, cache, time.Duration(math.MaxInt64), tr.Di r, []string{repoName}, isolateClient, swarmingClient)
592 assert.NoError(t, err)
593
594 // Ensure that the queue is initially empty.
595 assert.Equal(t, 0, len(s.queue))
596
597 // Regenerate the task queue.
598 assert.NoError(t, s.regenerateTaskQueue())
599 assert.Equal(t, 2, len(s.queue)) // Two Build tasks.
600
601 testSort := func() {
602 // Ensure that we sorted correctly.
603 if len(s.queue) == 0 {
604 return
605 }
606 highScore := s.queue[0].Score
607 for _, c := range s.queue {
608 assert.True(t, highScore >= c.Score)
609 highScore = c.Score
610 }
611 }
612 testSort()
613
614 // Since we haven't run any task yet, we should have the two Build
615 // tasks, each with a blamelist of 1 commit (since we don't go past
616 // taskCandidate.Revision when computing blamelists when we haven't run
617 // a given task spec before), and a score of 2.0.
618 for _, c := range s.queue {
619 assert.Equal(t, buildTask, c.Name)
620 assert.Equal(t, []string{c.Revision}, c.Commits)
621 assert.Equal(t, 2.0, c.Score)
622 }
623
624 // Insert one of the tasks.
625 var t1 *db.Task
626 for _, c := range s.queue { // Order not guaranteed; find the right cand idate.
627 if c.Revision == c1 {
628 t1 = makeTask(c.Name, c.Repo, c.Revision)
629 break
630 }
631 }
632 assert.NotNil(t, t1)
633 t1.Status = db.TASK_STATUS_SUCCESS
634 t1.IsolatedOutput = "fake isolated hash"
635 assert.NoError(t, d.PutTask(t1))
636 assert.NoError(t, cache.Update())
637
638 // Regenerate the task queue.
639 assert.NoError(t, s.regenerateTaskQueue())
640
641 // Now we expect the queue to contain the other Build task and the one
642 // Test task we unblocked by running the first Build task.
643 assert.Equal(t, 2, len(s.queue))
644 testSort()
645 for _, c := range s.queue {
646 assert.Equal(t, 2.0, c.Score)
647 assert.Equal(t, []string{c.Revision}, c.Commits)
648 }
649 buildIdx := 0
650 testIdx := 1
651 if s.queue[1].Name == buildTask {
652 buildIdx = 1
653 testIdx = 0
654 }
655 assert.Equal(t, buildTask, s.queue[buildIdx].Name)
656 assert.Equal(t, c2, s.queue[buildIdx].Revision)
657
658 assert.Equal(t, testTask, s.queue[testIdx].Name)
659 assert.Equal(t, c1, s.queue[testIdx].Revision)
660
661 // Run the other Build task.
662 t2 := makeTask(s.queue[buildIdx].Name, s.queue[buildIdx].Repo, s.queue[b uildIdx].Revision)
663 t2.Status = db.TASK_STATUS_SUCCESS
664 t2.IsolatedOutput = "fake isolated hash"
665 assert.NoError(t, d.PutTask(t2))
666 assert.NoError(t, cache.Update())
667
668 // Regenerate the task queue.
669 assert.NoError(t, s.regenerateTaskQueue())
670 assert.Equal(t, 3, len(s.queue))
671 testSort()
672 perfIdx := -1
673 for i, c := range s.queue {
674 if c.Name == perfTask {
675 perfIdx = i
676 } else {
677 assert.Equal(t, c.Name, testTask)
678 }
679 assert.Equal(t, 2.0, c.Score)
680 assert.Equal(t, []string{c.Revision}, c.Commits)
681 }
682 assert.True(t, perfIdx > -1)
683
684 // Run the Test task at tip of tree, but make its blamelist cover both
685 // commits.
686 t3 := makeTask(testTask, repoName, c2)
687 t3.Commits = append(t3.Commits, c1)
688 t3.Status = db.TASK_STATUS_SUCCESS
689 t3.IsolatedOutput = "fake isolated hash"
690 assert.NoError(t, d.PutTask(t3))
691 assert.NoError(t, cache.Update())
692
693 // Regenerate the task queue.
694 assert.NoError(t, s.regenerateTaskQueue())
695
696 // Now we expect the queue to contain one Test and one Perf task. The
697 // Test task is a backfill, and should have a score of 0.5.
698 assert.Equal(t, 2, len(s.queue))
699 testSort()
700 // First candidate should be the perf task.
701 assert.Equal(t, perfTask, s.queue[0].Name)
702 assert.Equal(t, 2.0, s.queue[0].Score)
703 // The test task is next, a backfill.
704 assert.Equal(t, testTask, s.queue[1].Name)
705 assert.Equal(t, 0.5, s.queue[1].Score)
706 }
707
708 func makeTaskCandidate(name string, dims []string) *taskCandidate {
709 return &taskCandidate{
710 Name: name,
711 TaskSpec: &TaskSpec{
712 Dimensions: dims,
713 },
714 }
715 }
716
717 func makeSwarmingBot(id string, dims []string) *swarming_api.SwarmingRpcsBotInfo {
718 d := make([]*swarming_api.SwarmingRpcsStringListPair, 0, len(dims))
719 for _, s := range dims {
720 split := strings.SplitN(s, ":", 2)
721 d = append(d, &swarming_api.SwarmingRpcsStringListPair{
722 Key: split[0],
723 Value: []string{split[1]},
724 })
725 }
726 return &swarming_api.SwarmingRpcsBotInfo{
727 BotId: id,
728 Dimensions: d,
729 }
730 }
731
732 func TestGetCandidatesToSchedule(t *testing.T) {
733 // Empty lists.
734 rv := getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{}, []* taskCandidate{})
735 assert.Equal(t, 0, len(rv))
736
737 t1 := makeTaskCandidate("task1", []string{"k:v"})
738 rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{}, []*t askCandidate{t1})
739 assert.Equal(t, 0, len(rv))
740
741 b1 := makeSwarmingBot("bot1", []string{"k:v"})
742 rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1}, [] *taskCandidate{})
743 assert.Equal(t, 0, len(rv))
744
745 // Single match.
746 rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1}, [] *taskCandidate{t1})
747 testutils.AssertDeepEqual(t, []*taskCandidate{t1}, rv)
748
749 // No match.
750 t1.TaskSpec.Dimensions[0] = "k:v2"
751 rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1}, [] *taskCandidate{t1})
752 assert.Equal(t, 0, len(rv))
753
754 // Add a task candidate to match b1.
755 t1 = makeTaskCandidate("task1", []string{"k:v2"})
756 t2 := makeTaskCandidate("task2", []string{"k:v"})
757 rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1}, [] *taskCandidate{t1, t2})
758 testutils.AssertDeepEqual(t, []*taskCandidate{t2}, rv)
759
760 // Switch the task order.
761 t1 = makeTaskCandidate("task1", []string{"k:v2"})
762 t2 = makeTaskCandidate("task2", []string{"k:v"})
763 rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1}, [] *taskCandidate{t2, t1})
764 testutils.AssertDeepEqual(t, []*taskCandidate{t2}, rv)
765
766 // Make both tasks match the bot, ensure that we pick the first one.
767 t1 = makeTaskCandidate("task1", []string{"k:v"})
768 t2 = makeTaskCandidate("task2", []string{"k:v"})
769 rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1}, [] *taskCandidate{t1, t2})
770 testutils.AssertDeepEqual(t, []*taskCandidate{t1}, rv)
771 rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1}, [] *taskCandidate{t2, t1})
772 testutils.AssertDeepEqual(t, []*taskCandidate{t2}, rv)
773
774 // Multiple dimensions. Ensure that different permutations of the bots
775 // and tasks lists give us the expected results.
776 dims := []string{"k:v", "k2:v2", "k3:v3"}
777 b1 = makeSwarmingBot("bot1", dims)
778 b2 := makeSwarmingBot("bot2", t1.TaskSpec.Dimensions)
779 t1 = makeTaskCandidate("task1", []string{"k:v"})
780 t2 = makeTaskCandidate("task2", dims)
781 // In the first two cases, the task with fewer dimensions has the
782 // higher priority. It gets the bot with more dimensions because it
783 // is first in sorted order. The second task does not get scheduled
784 // because there is no bot available which can run it.
785 // TODO(borenet): Use a more optimal solution to avoid this case.
786 rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1, b2} , []*taskCandidate{t1, t2})
787 testutils.AssertDeepEqual(t, []*taskCandidate{t1}, rv)
788 t1 = makeTaskCandidate("task1", []string{"k:v"})
789 t2 = makeTaskCandidate("task2", dims)
790 rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b2, b1} , []*taskCandidate{t1, t2})
791 testutils.AssertDeepEqual(t, []*taskCandidate{t1}, rv)
792 // In these two cases, the task with more dimensions has the higher
793 // priority. Both tasks get scheduled.
794 t1 = makeTaskCandidate("task1", []string{"k:v"})
795 t2 = makeTaskCandidate("task2", dims)
796 rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1, b2} , []*taskCandidate{t2, t1})
797 testutils.AssertDeepEqual(t, []*taskCandidate{t2, t1}, rv)
798 t1 = makeTaskCandidate("task1", []string{"k:v"})
799 t2 = makeTaskCandidate("task2", dims)
800 rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b2, b1} , []*taskCandidate{t2, t1})
801 testutils.AssertDeepEqual(t, []*taskCandidate{t2, t1}, rv)
802
803 // Matching dimensions. More bots than tasks.
804 b2 = makeSwarmingBot("bot2", dims)
805 b3 := makeSwarmingBot("bot3", dims)
806 t1 = makeTaskCandidate("task1", dims)
807 t2 = makeTaskCandidate("task2", dims)
808 t3 := makeTaskCandidate("task3", dims)
809 rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1, b2, b3}, []*taskCandidate{t1, t2})
810 testutils.AssertDeepEqual(t, []*taskCandidate{t1, t2}, rv)
811
812 // More tasks than bots.
813 t1 = makeTaskCandidate("task1", dims)
814 t2 = makeTaskCandidate("task2", dims)
815 t3 = makeTaskCandidate("task3", dims)
816 rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1, b2} , []*taskCandidate{t1, t2, t3})
817 testutils.AssertDeepEqual(t, []*taskCandidate{t1, t2}, rv)
818 }
819
820 func makeBot(id string, dims map[string]string) *swarming_api.SwarmingRpcsBotInf o {
821 dimensions := make([]*swarming_api.SwarmingRpcsStringListPair, 0, len(di ms))
822 for k, v := range dims {
823 dimensions = append(dimensions, &swarming_api.SwarmingRpcsString ListPair{
824 Key: k,
825 Value: []string{v},
826 })
827 }
828 return &swarming_api.SwarmingRpcsBotInfo{
829 BotId: id,
830 Dimensions: dimensions,
831 }
832 }
833
834 func TestSchedulingE2E(t *testing.T) {
835 testutils.SkipIfShort(t)
836
837 // Setup.
838 tr := util.NewTempRepo()
839 defer tr.Cleanup()
840 d := db.NewInMemoryDB()
841 cache, err := db.NewTaskCache(d, time.Hour)
842 assert.NoError(t, err)
843
844 // The test repo has two commits. The first commit adds a tasks.cfg file
845 // with two task specs: a build task and a test task, the test task
846 // depending on the build task. The second commit adds a perf task spec,
847 // which also depends on the build task. Therefore, there are five total
848 // possible tasks we could run:
849 //
850 // Build@c1, Test@c1, Build@c2, Test@c2, Perf@c2
851 //
852 c1 := "c06ac6093d3029dffe997e9d85e8e61fee5f87b9"
853 c2 := "0f87799ac791b8d8573e93694d05b05a65e09668"
854
855 repoName := "skia.git"
856
857 isolateClient, err := isolate.NewClient(tr.Dir)
858 assert.NoError(t, err)
859 isolateClient.ServerUrl = isolate.FAKE_SERVER_URL
860 swarmingClient := swarming.NewTestClient()
861 s, err := NewTaskScheduler(d, cache, time.Duration(math.MaxInt64), tr.Di r, []string{repoName}, isolateClient, swarmingClient)
862
863 // Start testing. No free bots, so we get a full queue with nothing
864 // scheduled.
865 assert.NoError(t, s.MainLoop())
866 tasks, err := cache.GetTasksForCommits(repoName, []string{c1, c2})
867 assert.NoError(t, err)
868 expect := map[string]map[string]*db.Task{
869 c1: map[string]*db.Task{},
870 c2: map[string]*db.Task{},
871 }
872 testutils.AssertDeepEqual(t, expect, tasks)
873
874 // A bot is free but doesn't have all of the right dimensions to run a t ask.
875 bot1 := makeBot("bot1", map[string]string{"pool": "Skia"})
876 swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1})
877 assert.NoError(t, s.MainLoop())
878 tasks, err = cache.GetTasksForCommits(repoName, []string{c1, c2})
879 assert.NoError(t, err)
880 expect = map[string]map[string]*db.Task{
881 c1: map[string]*db.Task{},
882 c2: map[string]*db.Task{},
883 }
884 testutils.AssertDeepEqual(t, expect, tasks)
885 assert.Equal(t, 2, len(s.queue))
886
887 // One bot free, schedule a task, ensure it's not in the queue.
888 bot1.Dimensions = append(bot1.Dimensions, &swarming_api.SwarmingRpcsStri ngListPair{
889 Key: "os",
890 Value: []string{"Ubuntu"},
891 })
892 swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1})
893 assert.NoError(t, s.MainLoop())
894 tasks, err = cache.GetTasksForCommits(repoName, []string{c1, c2})
895 assert.NoError(t, err)
896 var t1 *db.Task
897 for _, v := range tasks {
898 for _, t := range v {
899 t1 = t
900 break
901 }
902 }
903 assert.NotNil(t, t1)
904 assert.Equal(t, 1, len(s.queue))
905
906 // The task is complete.
907 t1.Status = db.TASK_STATUS_SUCCESS
908 t1.Finished = time.Now()
909 t1.IsolatedOutput = "abc123"
910 assert.NoError(t, d.PutTask(t1))
911 swarmingClient.MockTasks([]*swarming_api.SwarmingRpcsTaskRequestMetadata {
912 makeSwarmingRpcsTaskRequestMetadata(t, t1),
913 })
914
915 // No bots free. Ensure that the queue is correct.
916 swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{})
917 assert.NoError(t, s.MainLoop())
918 tasks, err = cache.GetTasksForCommits(repoName, []string{c1, c2})
919 assert.NoError(t, err)
920 // The tests don't use any time-based score scaling, because the commits
921 // in the test repo have fixed timestamps and would eventually result in
922 // zero scores. The side effect is that we don't know which of c1 or c2
923 // will be chosen because they end up with the same score.
924 expectLen := 2 // One remaining build task, plus one test task.
925 if t1.Revision == c2 {
926 expectLen = 3 // c2 adds a perf task.
927 }
928 assert.Equal(t, expectLen, len(s.queue))
929
930 // More bots than tasks free, ensure the queue is correct.
931 bot2 := makeBot("bot2", map[string]string{
932 "pool": "Skia",
933 "os": "Android",
934 "device_type": "grouper",
935 })
936 bot3 := makeBot("bot3", map[string]string{
937 "pool": "Skia",
938 "os": "Android",
939 "device_type": "grouper",
940 })
941 bot4 := makeBot("bot4", map[string]string{
942 "pool": "Skia",
943 "os": "Ubuntu",
944 })
945 swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1, bot2, bot3, bot4})
946 assert.NoError(t, s.MainLoop())
947 _, err = cache.GetTasksForCommits(repoName, []string{c1, c2})
948 assert.NoError(t, err)
949 assert.Equal(t, 0, len(s.queue))
950
951 // Second compile task finished.
952 var t2 *db.Task
953 var t3 *db.Task
954 var t4 *db.Task
955 tasks, err = cache.GetTasksForCommits(repoName, []string{c1, c2})
956 assert.NoError(t, err)
957 for _, v := range tasks {
958 for _, task := range v {
959 if task.Name == t1.Name {
960 if (t1.Revision == c1 && task.Revision == c2) || (t1.Revision == c2 && task.Revision == c1) {
961 t2 = task
962 }
963 } else {
964 if t3 == nil {
965 t3 = task
966 } else {
967 t4 = task
968 }
969 }
970 }
971 }
972 assert.NotNil(t, t2)
973 assert.NotNil(t, t3)
974 t2.Status = db.TASK_STATUS_SUCCESS
975 t2.Finished = time.Now()
976 t2.IsolatedOutput = "abc123"
977
978 // No new bots free; ensure that the newly-available tasks are in the qu eue.
979 swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{})
980 mockTasks := []*swarming_api.SwarmingRpcsTaskRequestMetadata{
981 makeSwarmingRpcsTaskRequestMetadata(t, t2),
982 makeSwarmingRpcsTaskRequestMetadata(t, t3),
983 }
984 if t4 != nil {
985 mockTasks = append(mockTasks, makeSwarmingRpcsTaskRequestMetadat a(t, t4))
986 }
987 swarmingClient.MockTasks(mockTasks)
988 assert.NoError(t, s.MainLoop())
989 expectLen = 1 // Test task from c1
990 if t2.Revision == c2 {
991 expectLen = 2 // Test and perf tasks from c2
992 }
993 assert.Equal(t, expectLen, len(s.queue))
994
995 // Finish the other tasks.
996 t3, err = cache.GetTask(t3.Id)
997 assert.NoError(t, err)
998 t3.Status = db.TASK_STATUS_SUCCESS
999 t3.Finished = time.Now()
1000 t3.IsolatedOutput = "abc123"
1001 if t4 != nil {
1002 t4, err = cache.GetTask(t4.Id)
1003 assert.NoError(t, err)
1004 t4.Status = db.TASK_STATUS_SUCCESS
1005 t4.Finished = time.Now()
1006 t4.IsolatedOutput = "abc123"
1007 }
1008
1009 // Ensure that we finally run all of the tasks and insert into the DB.
1010 swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1, bot2, bot3, bot4})
1011 mockTasks = []*swarming_api.SwarmingRpcsTaskRequestMetadata{
1012 makeSwarmingRpcsTaskRequestMetadata(t, t3),
1013 }
1014 if t4 != nil {
1015 mockTasks = append(mockTasks, makeSwarmingRpcsTaskRequestMetadat a(t, t4))
1016 }
1017 assert.NoError(t, s.MainLoop())
1018 tasks, err = cache.GetTasksForCommits(repoName, []string{c1, c2})
1019 assert.NoError(t, err)
1020 assert.Equal(t, 2, len(tasks[c1]))
1021 assert.Equal(t, 3, len(tasks[c2]))
1022 assert.Equal(t, 0, len(s.queue))
1023
1024 // Mark everything as finished. Ensure that the queue still ends up empt y.
1025 tasksList := []*db.Task{}
1026 for _, v := range tasks {
1027 for _, task := range v {
1028 if task.Status != db.TASK_STATUS_SUCCESS {
1029 task.Status = db.TASK_STATUS_SUCCESS
1030 task.Finished = time.Now()
1031 task.IsolatedOutput = "abc123"
1032 tasksList = append(tasksList, task)
1033 }
1034 }
1035 }
1036 mockTasks = make([]*swarming_api.SwarmingRpcsTaskRequestMetadata, 0, len (tasksList))
1037 for _, task := range tasksList {
1038 mockTasks = append(mockTasks, makeSwarmingRpcsTaskRequestMetadat a(t, task))
1039 }
1040 swarmingClient.MockTasks(mockTasks)
1041 swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1, bot2, bot3, bot4})
1042 assert.NoError(t, s.MainLoop())
1043 assert.Equal(t, 0, len(s.queue))
1044 }
1045
1046 func makeDummyCommits(t *testing.T, repoDir string, numCommits int) {
1047 _, err := exec.RunCwd(repoDir, "git", "config", "user.email", "test@skia .org")
1048 assert.NoError(t, err)
1049 _, err = exec.RunCwd(repoDir, "git", "config", "user.name", "Skia Tester ")
1050 assert.NoError(t, err)
1051 _, err = exec.RunCwd(repoDir, "git", "checkout", "master")
1052 assert.NoError(t, err)
1053 dummyFile := path.Join(repoDir, "dummyfile.txt")
1054 for i := 0; i < numCommits; i++ {
1055 title := fmt.Sprintf("Dummy #%d", i)
1056 assert.NoError(t, ioutil.WriteFile(dummyFile, []byte(title), os. ModePerm))
1057 _, err = exec.RunCwd(repoDir, "git", "add", dummyFile)
1058 assert.NoError(t, err)
1059 _, err = exec.RunCwd(repoDir, "git", "commit", "-m", title)
1060 assert.NoError(t, err)
1061 _, err = exec.RunCwd(repoDir, "git", "push", "origin", "master")
1062 assert.NoError(t, err)
1063 }
1064 }
1065
1066 func TestSchedulerStealingFrom(t *testing.T) {
1067 testutils.SkipIfShort(t)
1068
1069 // Setup.
1070 tr := util.NewTempRepo()
1071 d := db.NewInMemoryDB()
1072 cache, err := db.NewTaskCache(d, time.Hour)
1073 assert.NoError(t, err)
1074
1075 // The test repo has two commits. The first commit adds a tasks.cfg file
1076 // with two task specs: a build task and a test task, the test task
1077 // depending on the build task. The second commit adds a perf task spec,
1078 // which also depends on the build task. Therefore, there are five total
1079 // possible tasks we could run:
1080 //
1081 // Build@c1, Test@c1, Build@c2, Test@c2, Perf@c2
1082 //
1083 c1 := "c06ac6093d3029dffe997e9d85e8e61fee5f87b9"
1084 c2 := "0f87799ac791b8d8573e93694d05b05a65e09668"
1085 buildTask := "Build-Ubuntu-GCC-Arm7-Release-Android"
1086 repoName := "skia.git"
1087 repoDir := path.Join(tr.Dir, repoName)
1088
1089 repos := gitinfo.NewRepoMap(tr.Dir)
1090 repo, err := repos.Repo(repoName)
1091 assert.NoError(t, err)
1092 isolateClient, err := isolate.NewClient(tr.Dir)
1093 assert.NoError(t, err)
1094 isolateClient.ServerUrl = isolate.FAKE_SERVER_URL
1095 swarmingClient := swarming.NewTestClient()
1096 s, err := NewTaskScheduler(d, cache, time.Duration(math.MaxInt64), tr.Di r, []string{"skia.git"}, isolateClient, swarmingClient)
1097 assert.NoError(t, err)
1098
1099 // Run both available compile tasks.
1100 bot1 := makeBot("bot1", map[string]string{"pool": "Skia", "os": "Ubuntu" })
1101 bot2 := makeBot("bot2", map[string]string{"pool": "Skia", "os": "Ubuntu" })
1102 swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1, bot2})
1103 assert.NoError(t, s.MainLoop())
1104 tasks, err := cache.GetTasksForCommits(repoName, []string{c1, c2})
1105 assert.NoError(t, err)
1106 assert.Equal(t, 1, len(tasks[c1]))
1107 assert.Equal(t, 1, len(tasks[c2]))
1108 tasksList := []*db.Task{}
1109 for _, v := range tasks {
1110 for _, task := range v {
1111 if task.Status != db.TASK_STATUS_SUCCESS {
1112 task.Status = db.TASK_STATUS_SUCCESS
1113 task.Finished = time.Now()
1114 task.IsolatedOutput = "abc123"
1115 tasksList = append(tasksList, task)
1116 }
1117 }
1118 }
1119 assert.NoError(t, d.PutTasks(tasksList))
1120 assert.NoError(t, cache.Update())
1121
1122 // Add some commits.
1123 makeDummyCommits(t, repoDir, 10)
1124 commits, err := repo.RevList("HEAD")
1125 assert.NoError(t, err)
1126
1127 // Run one task. Ensure that it's at tip-of-tree.
1128 head, err := repo.FullHash("HEAD")
1129 assert.NoError(t, err)
1130 swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1})
1131 assert.NoError(t, s.MainLoop())
1132 tasks, err = cache.GetTasksForCommits(repoName, commits)
1133 assert.NoError(t, err)
1134 assert.Equal(t, 1, len(tasks[head]))
1135 task := tasks[head][buildTask]
1136 assert.Equal(t, head, task.Revision)
1137 expect := commits[:len(commits)-2]
1138 sort.Strings(expect)
1139 sort.Strings(task.Commits)
1140 testutils.AssertDeepEqual(t, expect, task.Commits)
1141
1142 task.Status = db.TASK_STATUS_SUCCESS
1143 task.Finished = time.Now()
1144 task.IsolatedOutput = "abc123"
1145 assert.NoError(t, d.PutTask(task))
1146 assert.NoError(t, cache.Update())
1147
1148 oldTasksByCommit := tasks
1149
1150 // Run backfills, ensuring that each one steals the right set of commits
1151 // from previous builds, until all of the build task candidates have run .
1152 for i := 0; i < 9; i++ {
1153 // Now, run another task. The new task should bisect the old one .
1154 swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1 })
1155 assert.NoError(t, s.MainLoop())
1156 tasks, err = cache.GetTasksForCommits(repoName, commits)
1157 assert.NoError(t, err)
1158 var newTask *db.Task
1159 for _, v := range tasks {
1160 for _, task := range v {
1161 if task.Status == db.TASK_STATUS_PENDING {
1162 assert.True(t, newTask == nil || task.Id == newTask.Id)
1163 newTask = task
1164 }
1165 }
1166 }
1167 assert.NotNil(t, newTask)
1168
1169 oldTask := oldTasksByCommit[newTask.Revision][newTask.Name]
1170 assert.NotNil(t, oldTask)
1171 assert.True(t, util.In(newTask.Revision, oldTask.Commits))
1172
1173 // Find the updated old task.
1174 updatedOldTask, err := cache.GetTask(oldTask.Id)
1175 assert.NoError(t, err)
1176 assert.NotNil(t, updatedOldTask)
1177
1178 // Ensure that the blamelists are correct.
1179 old := util.NewStringSet(oldTask.Commits)
1180 new := util.NewStringSet(newTask.Commits)
1181 updatedOld := util.NewStringSet(updatedOldTask.Commits)
1182
1183 testutils.AssertDeepEqual(t, old, new.Union(updatedOld))
1184 assert.Equal(t, 0, len(new.Intersect(updatedOld)))
1185 // Finish the new task.
1186 newTask.Status = db.TASK_STATUS_SUCCESS
1187 newTask.Finished = time.Now()
1188 newTask.IsolatedOutput = "abc123"
1189 assert.NoError(t, d.PutTask(newTask))
1190 assert.NoError(t, cache.Update())
1191 oldTasksByCommit = tasks
1192
1193 }
1194
1195 // Ensure that we're really done.
1196 swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1})
1197 assert.NoError(t, s.MainLoop())
1198 tasks, err = cache.GetTasksForCommits(repoName, commits)
1199 assert.NoError(t, err)
1200 var newTask *db.Task
1201 for _, v := range tasks {
1202 for _, task := range v {
1203 if task.Status == db.TASK_STATUS_PENDING {
1204 assert.True(t, newTask == nil || task.Id == newT ask.Id)
1205 newTask = task
1206 }
1207 }
1208 }
1209 assert.Nil(t, newTask)
1210 }
1211
1212 func TestMultipleCandidatesBackfillingEachOther(t *testing.T) {
1213 testutils.SkipIfShort(t)
1214
1215 workdir, err := ioutil.TempDir("", "")
1216 assert.NoError(t, err)
1217 defer testutils.RemoveAll(t, workdir)
1218
1219 run := func(dir string, cmd ...string) {
1220 _, err := exec.RunCwd(dir, cmd...)
1221 assert.NoError(t, err)
1222 }
1223
1224 addFile := func(repoDir, subPath, contents string) {
1225 assert.NoError(t, ioutil.WriteFile(path.Join(repoDir, subPath), []byte(contents), os.ModePerm))
1226 run(repoDir, "git", "add", subPath)
1227 }
1228
1229 repoName := "skia.git"
1230 repoDir := path.Join(workdir, repoName)
1231
1232 assert.NoError(t, ioutil.WriteFile(path.Join(workdir, ".gclient"), []byt e("dummy"), os.ModePerm))
1233
1234 assert.NoError(t, os.Mkdir(path.Join(workdir, repoName), os.ModePerm))
1235 run(repoDir, "git", "init")
1236 run(repoDir, "git", "remote", "add", "origin", ".")
1237
1238 infraBotsSubDir := path.Join("infra", "bots")
1239 infraBotsDir := path.Join(repoDir, infraBotsSubDir)
1240 assert.NoError(t, os.MkdirAll(infraBotsDir, os.ModePerm))
1241
1242 addFile(repoDir, "somefile.txt", "dummy3")
1243 addFile(repoDir, path.Join(infraBotsSubDir, "dummy.isolate"), `{
1244 'variables': {
1245 'command': [
1246 'python', 'recipes.py', 'run',
1247 ],
1248 'files': [
1249 '../../somefile.txt',
1250 ],
1251 },
1252 }`)
1253
1254 // Create a single task in the config.
1255 taskName := "dummytask"
1256 cfg := &TasksCfg{
1257 Tasks: map[string]*TaskSpec{
1258 taskName: &TaskSpec{
1259 CipdPackages: []*CipdPackage{},
1260 Dependencies: []string{},
1261 Dimensions: []string{"pool:Skia"},
1262 Isolate: "dummy.isolate",
1263 Priority: 1.0,
1264 },
1265 },
1266 }
1267 f, err := os.Create(path.Join(repoDir, TASKS_CFG_FILE))
1268 assert.NoError(t, err)
1269 assert.NoError(t, json.NewEncoder(f).Encode(&cfg))
1270 assert.NoError(t, f.Close())
1271 run(repoDir, "git", "add", TASKS_CFG_FILE)
1272 run(repoDir, "git", "commit", "-m", "Add more tasks!")
1273 run(repoDir, "git", "push", "origin", "master")
1274 run(repoDir, "git", "branch", "-u", "origin/master")
1275
1276 // Setup the scheduler.
1277 repos := gitinfo.NewRepoMap(workdir)
1278 repo, err := repos.Repo(repoName)
1279 assert.NoError(t, err)
1280 d := db.NewInMemoryDB()
1281 cache, err := db.NewTaskCache(d, time.Hour)
1282 assert.NoError(t, err)
1283 isolateClient, err := isolate.NewClient(workdir)
1284 assert.NoError(t, err)
1285 isolateClient.ServerUrl = isolate.FAKE_SERVER_URL
1286 swarmingClient := swarming.NewTestClient()
1287 s, err := NewTaskScheduler(d, cache, time.Duration(math.MaxInt64), workd ir, []string{repoName}, isolateClient, swarmingClient)
1288 assert.NoError(t, err)
1289
1290 mockTasks := []*swarming_api.SwarmingRpcsTaskRequestMetadata{}
1291 mock := func(task *db.Task) {
1292 mockTasks = append(mockTasks, makeSwarmingRpcsTaskRequestMetadat a(t, task))
1293 swarmingClient.MockTasks(mockTasks)
1294 }
1295
1296 // Cycle once.
1297 bot1 := makeBot("bot1", map[string]string{"pool": "Skia"})
1298 swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1})
1299 assert.NoError(t, s.MainLoop())
1300 assert.Equal(t, 0, len(s.queue))
1301 head, err := repo.FullHash("HEAD")
1302 assert.NoError(t, err)
1303 tasks, err := cache.GetTasksForCommits(repoName, []string{head})
1304 assert.NoError(t, err)
1305 assert.Equal(t, 1, len(tasks[head]))
1306 mock(tasks[head][taskName])
1307
1308 // Add some commits to the repo.
1309 makeDummyCommits(t, repoDir, 8)
1310 commits, err := repo.RevList(fmt.Sprintf("%s..HEAD", head))
1311 assert.Nil(t, err)
1312 assert.Equal(t, 8, len(commits))
1313
1314 // Trigger builds simultaneously.
1315 bot2 := makeBot("bot2", map[string]string{"pool": "Skia"})
1316 bot3 := makeBot("bot3", map[string]string{"pool": "Skia"})
1317 swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1, bot2, bot3})
1318 assert.NoError(t, s.MainLoop())
1319 assert.Equal(t, 5, len(s.queue))
1320 tasks, err = cache.GetTasksForCommits(repoName, commits)
1321 assert.NoError(t, err)
1322
1323 // If we're queueing correctly, we should've triggered tasks at
1324 // commits[0], commits[4], and either commits[2] or commits[6].
1325 var t1, t2, t3 *db.Task
1326 for _, byName := range tasks {
1327 for _, task := range byName {
1328 if task.Revision == commits[0] {
1329 t1 = task
1330 } else if task.Revision == commits[4] {
1331 t2 = task
1332 } else if task.Revision == commits[2] || task.Revision = = commits[6] {
1333 t3 = task
1334 } else {
1335 assert.FailNow(t, fmt.Sprintf("Task has unknown revision: %v", task))
1336 }
1337 }
1338 }
1339 assert.NotNil(t, t1)
1340 assert.NotNil(t, t2)
1341 assert.NotNil(t, t3)
1342 mock(t1)
1343 mock(t2)
1344 mock(t3)
1345
1346 // Ensure that we got the blamelists right.
1347 mkCopy := func(orig []string) []string {
1348 rv := make([]string, len(orig))
1349 copy(rv, orig)
1350 return rv
1351 }
1352 var expect1, expect2, expect3 []string
1353 if t3.Revision == commits[2] {
1354 expect1 = mkCopy(commits[:2])
1355 expect2 = mkCopy(commits[4:])
1356 expect3 = mkCopy(commits[2:4])
1357 } else {
1358 expect1 = mkCopy(commits[:4])
1359 expect2 = mkCopy(commits[4:6])
1360 expect3 = mkCopy(commits[6:])
1361 }
1362 sort.Strings(expect1)
1363 sort.Strings(expect2)
1364 sort.Strings(expect3)
1365 sort.Strings(t1.Commits)
1366 sort.Strings(t2.Commits)
1367 sort.Strings(t3.Commits)
1368 testutils.AssertDeepEqual(t, expect1, t1.Commits)
1369 testutils.AssertDeepEqual(t, expect2, t2.Commits)
1370 testutils.AssertDeepEqual(t, expect3, t3.Commits)
1371
1372 // Just for good measure, check the task at the head of the queue.
1373 expectIdx := 2
1374 if t3.Revision == commits[expectIdx] {
1375 expectIdx = 6
1376 }
1377 assert.Equal(t, commits[expectIdx], s.queue[0].Revision)
1378
1379 // Run again with 5 bots to check the case where we bisect the same
1380 // task twice.
1381 bot4 := makeBot("bot4", map[string]string{"pool": "Skia"})
1382 bot5 := makeBot("bot5", map[string]string{"pool": "Skia"})
1383 swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1, bot2, bot3, bot4, bot5})
1384 assert.NoError(t, s.MainLoop())
1385 assert.Equal(t, 0, len(s.queue))
1386 tasks, err = cache.GetTasksForCommits(repoName, commits)
1387 assert.NoError(t, err)
1388 for _, byName := range tasks {
1389 for _, task := range byName {
1390 assert.Equal(t, 1, len(task.Commits))
1391 }
1392 }
1393 }
OLDNEW
« no previous file with comments | « build_scheduler/go/task_scheduler/task_scheduler.go ('k') | build_scheduler/go/task_scheduler/testdata/testrepo.zip » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698