Index: build_scheduler/go/task_scheduler/task_scheduler_test.go |
diff --git a/build_scheduler/go/task_scheduler/task_scheduler_test.go b/build_scheduler/go/task_scheduler/task_scheduler_test.go |
deleted file mode 100644 |
index ac5b934eaa9548dfe7b22f40b03ed72c0224c86f..0000000000000000000000000000000000000000 |
--- a/build_scheduler/go/task_scheduler/task_scheduler_test.go |
+++ /dev/null |
@@ -1,1393 +0,0 @@ |
-package task_scheduler |
- |
-import ( |
- "encoding/json" |
- "fmt" |
- "io/ioutil" |
- "math" |
- "os" |
- "path" |
- "path/filepath" |
- "runtime" |
- "sort" |
- "strings" |
- "testing" |
- "time" |
- |
- swarming_api "github.com/luci/luci-go/common/api/swarming/swarming/v1" |
- assert "github.com/stretchr/testify/require" |
- "go.skia.org/infra/build_scheduler/go/db" |
- "go.skia.org/infra/go/buildbot" |
- "go.skia.org/infra/go/exec" |
- "go.skia.org/infra/go/gitinfo" |
- "go.skia.org/infra/go/gitrepo" |
- "go.skia.org/infra/go/isolate" |
- "go.skia.org/infra/go/swarming" |
- "go.skia.org/infra/go/testutils" |
- "go.skia.org/infra/go/util" |
-) |
- |
-func makeTask(name, repo, revision string) *db.Task { |
- return &db.Task{ |
- Commits: []string{revision}, |
- Created: time.Now(), |
- Name: name, |
- Repo: repo, |
- Revision: revision, |
- } |
-} |
- |
-func makeSwarmingRpcsTaskRequestMetadata(t *testing.T, task *db.Task) *swarming_api.SwarmingRpcsTaskRequestMetadata { |
- tag := func(k, v string) string { |
- return fmt.Sprintf("%s:%s", k, v) |
- } |
- ts := func(t time.Time) string { |
- return t.Format(swarming.TIMESTAMP_FORMAT) |
- } |
- abandoned := "" |
- state := db.SWARMING_STATE_PENDING |
- failed := false |
- switch task.Status { |
- case db.TASK_STATUS_MISHAP: |
- state = db.SWARMING_STATE_BOT_DIED |
- abandoned = ts(task.Finished) |
- case db.TASK_STATUS_RUNNING: |
- state = db.SWARMING_STATE_RUNNING |
- case db.TASK_STATUS_FAILURE: |
- state = db.SWARMING_STATE_COMPLETED |
- failed = true |
- case db.TASK_STATUS_SUCCESS: |
- state = db.SWARMING_STATE_COMPLETED |
- case db.TASK_STATUS_PENDING: |
- // noop |
- default: |
- assert.FailNow(t, "Unknown task status: %s", task.Status) |
- } |
- return &swarming_api.SwarmingRpcsTaskRequestMetadata{ |
- Request: &swarming_api.SwarmingRpcsTaskRequest{}, |
- TaskId: task.SwarmingTaskId, |
- TaskResult: &swarming_api.SwarmingRpcsTaskResult{ |
- AbandonedTs: abandoned, |
- CreatedTs: ts(task.Created), |
- CompletedTs: ts(task.Finished), |
- Failure: failed, |
- OutputsRef: &swarming_api.SwarmingRpcsFilesRef{ |
- Isolated: "???", |
- }, |
- StartedTs: ts(task.Started), |
- State: state, |
- Tags: []string{ |
- tag(db.SWARMING_TAG_ID, task.Id), |
- tag(db.SWARMING_TAG_NAME, task.Name), |
- tag(db.SWARMING_TAG_REPO, task.Repo), |
- tag(db.SWARMING_TAG_REVISION, task.Revision), |
- }, |
- TaskId: task.SwarmingTaskId, |
- }, |
- } |
-} |
- |
-func TestFindTaskCandidates(t *testing.T) { |
- testutils.SkipIfShort(t) |
- |
- // Setup. |
- tr := util.NewTempRepo() |
- defer tr.Cleanup() |
- d := db.NewInMemoryDB() |
- cache, err := db.NewTaskCache(d, time.Hour) |
- assert.NoError(t, err) |
- |
- // The test repo has two commits. The first commit adds a tasks.cfg file |
- // with two task specs: a build task and a test task, the test task |
- // depending on the build task. The second commit adds a perf task spec, |
- // which also depends on the build task. Therefore, there are five total |
- // possible tasks we could run: |
- // |
- // Build@c1, Test@c1, Build@c2, Test@c2, Perf@c2 |
- // |
- c1 := "c06ac6093d3029dffe997e9d85e8e61fee5f87b9" |
- c2 := "0f87799ac791b8d8573e93694d05b05a65e09668" |
- buildTask := "Build-Ubuntu-GCC-Arm7-Release-Android" |
- testTask := "Test-Android-GCC-Nexus7-GPU-Tegra3-Arm7-Release" |
- perfTask := "Perf-Android-GCC-Nexus7-GPU-Tegra3-Arm7-Release" |
- repo := "skia.git" |
- commits := map[string][]string{ |
- repo: []string{c1, c2}, |
- } |
- |
- assert.NoError(t, err) |
- isolateClient, err := isolate.NewClient(tr.Dir) |
- assert.NoError(t, err) |
- isolateClient.ServerUrl = isolate.FAKE_SERVER_URL |
- swarmingClient := swarming.NewTestClient() |
- s, err := NewTaskScheduler(d, cache, time.Duration(math.MaxInt64), tr.Dir, []string{"skia.git"}, isolateClient, swarmingClient) |
- assert.NoError(t, err) |
- |
- // Check the initial set of task candidates. The two Build tasks |
- // should be the only ones available. |
- c, err := s.findTaskCandidates(commits) |
- assert.NoError(t, err) |
- assert.Equal(t, 1, len(c)) |
- key := fmt.Sprintf("%s|%s", repo, buildTask) |
- assert.Equal(t, 2, len(c[key])) |
- for _, candidate := range c[key] { |
- assert.Equal(t, candidate.Name, buildTask) |
- } |
- |
- // Insert a the Build task at c1 (1 dependent) into the database, |
- // transition through various states. |
- var t1 *db.Task |
- for _, candidates := range c { // Order not guaranteed; find the right candidate. |
- for _, candidate := range candidates { |
- if candidate.Revision == c1 { |
- t1 = makeTask(candidate.Name, candidate.Repo, candidate.Revision) |
- break |
- } |
- } |
- } |
- assert.NotNil(t, t1) |
- |
- // We shouldn't duplicate pending or running tasks. |
- for _, status := range []db.TaskStatus{db.TASK_STATUS_PENDING, db.TASK_STATUS_RUNNING} { |
- t1.Status = status |
- assert.NoError(t, d.PutTask(t1)) |
- assert.NoError(t, cache.Update()) |
- |
- c, err = s.findTaskCandidates(commits) |
- assert.NoError(t, err) |
- assert.Equal(t, 1, len(c)) |
- for _, candidates := range c { |
- for _, candidate := range candidates { |
- assert.Equal(t, candidate.Name, buildTask) |
- assert.Equal(t, c2, candidate.Revision) |
- } |
- } |
- } |
- |
- // The task failed. Ensure that its dependents are not candidates, but |
- // the task itself is back in the list of candidates, in case we want |
- // to retry. |
- t1.Status = db.TASK_STATUS_FAILURE |
- assert.NoError(t, d.PutTask(t1)) |
- assert.NoError(t, cache.Update()) |
- |
- c, err = s.findTaskCandidates(commits) |
- assert.NoError(t, err) |
- assert.Equal(t, 1, len(c)) |
- for _, candidates := range c { |
- assert.Equal(t, 2, len(candidates)) |
- for _, candidate := range candidates { |
- assert.Equal(t, candidate.Name, buildTask) |
- } |
- } |
- |
- // The task succeeded. Ensure that its dependents are candidates and |
- // the task itself is not. |
- t1.Status = db.TASK_STATUS_SUCCESS |
- t1.IsolatedOutput = "fake isolated hash" |
- assert.NoError(t, d.PutTask(t1)) |
- assert.NoError(t, cache.Update()) |
- |
- c, err = s.findTaskCandidates(commits) |
- assert.NoError(t, err) |
- assert.Equal(t, 2, len(c)) |
- for _, candidates := range c { |
- for _, candidate := range candidates { |
- assert.False(t, t1.Name == candidate.Name && t1.Revision == candidate.Revision) |
- } |
- } |
- |
- // Create the other Build task. |
- var t2 *db.Task |
- for _, candidates := range c { |
- for _, candidate := range candidates { |
- if candidate.Revision == c2 && strings.HasPrefix(candidate.Name, "Build-") { |
- t2 = makeTask(candidate.Name, candidate.Repo, candidate.Revision) |
- break |
- } |
- } |
- } |
- assert.NotNil(t, t2) |
- t2.Status = db.TASK_STATUS_SUCCESS |
- t2.IsolatedOutput = "fake isolated hash" |
- assert.NoError(t, d.PutTask(t2)) |
- assert.NoError(t, cache.Update()) |
- |
- // All test and perf tasks are now candidates, no build tasks. |
- c, err = s.findTaskCandidates(commits) |
- assert.NoError(t, err) |
- assert.Equal(t, 2, len(c)) |
- assert.Equal(t, 2, len(c[fmt.Sprintf("%s|%s", repo, testTask)])) |
- assert.Equal(t, 1, len(c[fmt.Sprintf("%s|%s", repo, perfTask)])) |
- for _, candidates := range c { |
- for _, candidate := range candidates { |
- assert.NotEqual(t, candidate.Name, buildTask) |
- } |
- } |
-} |
- |
-func TestTestedness(t *testing.T) { |
- tc := []struct { |
- in int |
- out float64 |
- }{ |
- { |
- in: -1, |
- out: -1.0, |
- }, |
- { |
- in: 0, |
- out: 0.0, |
- }, |
- { |
- in: 1, |
- out: 1.0, |
- }, |
- { |
- in: 2, |
- out: 1.0 + 1.0/2.0, |
- }, |
- { |
- in: 3, |
- out: 1.0 + float64(2.0)/float64(3.0), |
- }, |
- { |
- in: 4, |
- out: 1.0 + 3.0/4.0, |
- }, |
- { |
- in: 4096, |
- out: 1.0 + float64(4095)/float64(4096), |
- }, |
- } |
- for i, c := range tc { |
- assert.Equal(t, c.out, testedness(c.in), fmt.Sprintf("test case #%d", i)) |
- } |
-} |
- |
-func TestTestednessIncrease(t *testing.T) { |
- tc := []struct { |
- a int |
- b int |
- out float64 |
- }{ |
- // Invalid cases. |
- { |
- a: -1, |
- b: 10, |
- out: -1.0, |
- }, |
- { |
- a: 10, |
- b: -1, |
- out: -1.0, |
- }, |
- { |
- a: 0, |
- b: -1, |
- out: -1.0, |
- }, |
- { |
- a: 0, |
- b: 0, |
- out: -1.0, |
- }, |
- // Invalid because if we're re-running at already-tested commits |
- // then we should have a blamelist which is at most the size of |
- // the blamelist of the previous task. We naturally get negative |
- // testedness increase in these cases. |
- { |
- a: 2, |
- b: 1, |
- out: -0.5, |
- }, |
- // Testing only new commits. |
- { |
- a: 1, |
- b: 0, |
- out: 1.0 + 1.0, |
- }, |
- { |
- a: 2, |
- b: 0, |
- out: 2.0 + (1.0 + 1.0/2.0), |
- }, |
- { |
- a: 3, |
- b: 0, |
- out: 3.0 + (1.0 + float64(2.0)/float64(3.0)), |
- }, |
- { |
- a: 4096, |
- b: 0, |
- out: 4096.0 + (1.0 + float64(4095.0)/float64(4096.0)), |
- }, |
- // Retries. |
- { |
- a: 1, |
- b: 1, |
- out: 0.0, |
- }, |
- { |
- a: 2, |
- b: 2, |
- out: 0.0, |
- }, |
- { |
- a: 3, |
- b: 3, |
- out: 0.0, |
- }, |
- { |
- a: 4096, |
- b: 4096, |
- out: 0.0, |
- }, |
- // Bisect/backfills. |
- { |
- a: 1, |
- b: 2, |
- out: 0.5, // (1 + 1) - (1 + 1/2) |
- }, |
- { |
- a: 1, |
- b: 3, |
- out: float64(2.5) - (1.0 + float64(2.0)/float64(3.0)), |
- }, |
- { |
- a: 5, |
- b: 10, |
- out: 2.0*(1.0+float64(4.0)/float64(5.0)) - (1.0 + float64(9.0)/float64(10.0)), |
- }, |
- } |
- for i, c := range tc { |
- assert.Equal(t, c.out, testednessIncrease(c.a, c.b), fmt.Sprintf("test case #%d", i)) |
- } |
-} |
- |
-func TestComputeBlamelist(t *testing.T) { |
- testutils.SkipIfShort(t) |
- |
- // Setup. |
- _, filename, _, _ := runtime.Caller(0) |
- // Use the test repo from the buildbot package, since it's already set |
- // up for this type of test. |
- zipfile := filepath.Join(filepath.Dir(filename), "..", "..", "..", "go", "buildbot", "testdata", "testrepo.zip") |
- tr := util.NewTempRepoFrom(zipfile) |
- defer tr.Cleanup() |
- d := db.NewInMemoryDB() |
- cache, err := db.NewTaskCache(d, time.Hour) |
- assert.NoError(t, err) |
- |
- // The test repo is laid out like this: |
- // |
- // * 06eb2a58139d3ff764f10232d5c8f9362d55e20f I (HEAD, master, Task #4) |
- // * ecb424466a4f3b040586a062c15ed58356f6590e F (Task #3) |
- // |\ |
- // | * d30286d2254716d396073c177a754f9e152bbb52 H |
- // | * 8d2d1247ef5d2b8a8d3394543df6c12a85881296 G (Task #2) |
- // * | 67635e7015d74b06c00154f7061987f426349d9f E |
- // * | 6d4811eddfa637fac0852c3a0801b773be1f260d D (Task #1) |
- // * | d74dfd42a48325ab2f3d4a97278fc283036e0ea4 C (Task #6) |
- // |/ |
- // * 4b822ebb7cedd90acbac6a45b897438746973a87 B (Task #0) |
- // * 051955c355eb742550ddde4eccc3e90b6dc5b887 A |
- // |
- hashes := map[rune]string{ |
- 'A': "051955c355eb742550ddde4eccc3e90b6dc5b887", |
- 'B': "4b822ebb7cedd90acbac6a45b897438746973a87", |
- 'C': "d74dfd42a48325ab2f3d4a97278fc283036e0ea4", |
- 'D': "6d4811eddfa637fac0852c3a0801b773be1f260d", |
- 'E': "67635e7015d74b06c00154f7061987f426349d9f", |
- 'F': "ecb424466a4f3b040586a062c15ed58356f6590e", |
- 'G': "8d2d1247ef5d2b8a8d3394543df6c12a85881296", |
- 'H': "d30286d2254716d396073c177a754f9e152bbb52", |
- 'I': "06eb2a58139d3ff764f10232d5c8f9362d55e20f", |
- } |
- |
- // Test cases. Each test case builds on the previous cases. |
- testCases := []struct { |
- Revision string |
- Expected []string |
- StoleFromIdx int |
- }{ |
- // 0. The first task. |
- { |
- Revision: hashes['B'], |
- Expected: []string{hashes['B']}, // Task #0 is limited to a single commit. |
- StoleFromIdx: -1, |
- }, |
- // 1. On a linear set of commits, with at least one previous task. |
- { |
- Revision: hashes['D'], |
- Expected: []string{hashes['D'], hashes['C']}, |
- StoleFromIdx: -1, |
- }, |
- // 2. The first task on a new branch. |
- { |
- Revision: hashes['G'], |
- Expected: []string{hashes['G']}, |
- StoleFromIdx: -1, |
- }, |
- // 3. After a merge. |
- { |
- Revision: hashes['F'], |
- Expected: []string{hashes['E'], hashes['H'], hashes['F']}, |
- StoleFromIdx: -1, |
- }, |
- // 4. One last "normal" task. |
- { |
- Revision: hashes['I'], |
- Expected: []string{hashes['I']}, |
- StoleFromIdx: -1, |
- }, |
- // 5. No Revision. |
- { |
- Revision: "", |
- Expected: []string{}, |
- StoleFromIdx: -1, |
- }, |
- // 6. Steal commits from a previously-ingested task. |
- { |
- Revision: hashes['C'], |
- Expected: []string{hashes['C']}, |
- StoleFromIdx: 1, |
- }, |
- } |
- name := "Test-Ubuntu12-ShuttleA-GTX660-x86-Release" |
- repoName := "skia.git" |
- repo, err := gitrepo.NewRepo(repoName, path.Join(tr.Dir, repoName)) |
- assert.NoError(t, err) |
- ids := make([]string, len(testCases)) |
- commitsBuf := make([]*gitrepo.Commit, 0, buildbot.MAX_BLAMELIST_COMMITS) |
- for i, tc := range testCases { |
- // Ensure that we get the expected blamelist. |
- commits, stoleFrom, err := ComputeBlamelist(cache, repo, name, repoName, tc.Revision, commitsBuf) |
- if tc.Revision == "" { |
- assert.Error(t, err) |
- continue |
- } else { |
- assert.NoError(t, err) |
- } |
- sort.Strings(commits) |
- testutils.AssertDeepEqual(t, tc.Expected, commits) |
- if tc.StoleFromIdx >= 0 { |
- assert.NotNil(t, stoleFrom) |
- assert.Equal(t, ids[tc.StoleFromIdx], stoleFrom.Id) |
- } else { |
- assert.Nil(t, stoleFrom) |
- } |
- |
- // Insert the task into the DB. |
- c := &taskCandidate{ |
- Name: name, |
- Repo: repoName, |
- Revision: tc.Revision, |
- } |
- task := c.MakeTask() |
- task.Commits = commits |
- task.Created = time.Now() |
- if stoleFrom != nil { |
- // Re-insert the stoleFrom task without the commits |
- // which were stolen from it. |
- stoleFromCommits := make([]string, 0, len(stoleFrom.Commits)-len(commits)) |
- for _, commit := range stoleFrom.Commits { |
- if !util.In(commit, task.Commits) { |
- stoleFromCommits = append(stoleFromCommits, commit) |
- } |
- } |
- stoleFrom.Commits = stoleFromCommits |
- assert.NoError(t, d.PutTasks([]*db.Task{task, stoleFrom})) |
- } else { |
- assert.NoError(t, d.PutTask(task)) |
- } |
- ids[i] = task.Id |
- assert.NoError(t, cache.Update()) |
- } |
- |
- // Extra: ensure that task #6 really stole the commit from #1. |
- task, err := cache.GetTask(ids[1]) |
- assert.NoError(t, err) |
- assert.False(t, util.In(hashes['C'], task.Commits), fmt.Sprintf("Expected not to find %s in %v", hashes['C'], task.Commits)) |
-} |
- |
-func TestTimeDecay24Hr(t *testing.T) { |
- tc := []struct { |
- decayAmt24Hr float64 |
- elapsed time.Duration |
- out float64 |
- }{ |
- { |
- decayAmt24Hr: 1.0, |
- elapsed: 10 * time.Hour, |
- out: 1.0, |
- }, |
- { |
- decayAmt24Hr: 0.5, |
- elapsed: 0 * time.Hour, |
- out: 1.0, |
- }, |
- { |
- decayAmt24Hr: 0.5, |
- elapsed: 24 * time.Hour, |
- out: 0.5, |
- }, |
- { |
- decayAmt24Hr: 0.5, |
- elapsed: 12 * time.Hour, |
- out: 0.75, |
- }, |
- { |
- decayAmt24Hr: 0.5, |
- elapsed: 36 * time.Hour, |
- out: 0.25, |
- }, |
- { |
- decayAmt24Hr: 0.5, |
- elapsed: 48 * time.Hour, |
- out: 0.0, |
- }, |
- { |
- decayAmt24Hr: 0.5, |
- elapsed: 72 * time.Hour, |
- out: 0.0, |
- }, |
- } |
- for i, c := range tc { |
- assert.Equal(t, c.out, timeDecay24Hr(c.decayAmt24Hr, c.elapsed), fmt.Sprintf("test case #%d", i)) |
- } |
-} |
- |
-func TestRegenerateTaskQueue(t *testing.T) { |
- testutils.SkipIfShort(t) |
- |
- // Setup. |
- tr := util.NewTempRepo() |
- defer tr.Cleanup() |
- d := db.NewInMemoryDB() |
- cache, err := db.NewTaskCache(d, time.Hour) |
- assert.NoError(t, err) |
- |
- // The test repo has two commits. The first commit adds a tasks.cfg file |
- // with two task specs: a build task and a test task, the test task |
- // depending on the build task. The second commit adds a perf task spec, |
- // which also depends on the build task. Therefore, there are five total |
- // possible tasks we could run: |
- // |
- // Build@c1, Test@c1, Build@c2, Test@c2, Perf@c2 |
- // |
- c1 := "c06ac6093d3029dffe997e9d85e8e61fee5f87b9" |
- c2 := "0f87799ac791b8d8573e93694d05b05a65e09668" |
- buildTask := "Build-Ubuntu-GCC-Arm7-Release-Android" |
- testTask := "Test-Android-GCC-Nexus7-GPU-Tegra3-Arm7-Release" |
- perfTask := "Perf-Android-GCC-Nexus7-GPU-Tegra3-Arm7-Release" |
- repoName := "skia.git" |
- |
- assert.NoError(t, err) |
- isolateClient, err := isolate.NewClient(tr.Dir) |
- assert.NoError(t, err) |
- isolateClient.ServerUrl = isolate.FAKE_SERVER_URL |
- swarmingClient := swarming.NewTestClient() |
- s, err := NewTaskScheduler(d, cache, time.Duration(math.MaxInt64), tr.Dir, []string{repoName}, isolateClient, swarmingClient) |
- assert.NoError(t, err) |
- |
- // Ensure that the queue is initially empty. |
- assert.Equal(t, 0, len(s.queue)) |
- |
- // Regenerate the task queue. |
- assert.NoError(t, s.regenerateTaskQueue()) |
- assert.Equal(t, 2, len(s.queue)) // Two Build tasks. |
- |
- testSort := func() { |
- // Ensure that we sorted correctly. |
- if len(s.queue) == 0 { |
- return |
- } |
- highScore := s.queue[0].Score |
- for _, c := range s.queue { |
- assert.True(t, highScore >= c.Score) |
- highScore = c.Score |
- } |
- } |
- testSort() |
- |
- // Since we haven't run any task yet, we should have the two Build |
- // tasks, each with a blamelist of 1 commit (since we don't go past |
- // taskCandidate.Revision when computing blamelists when we haven't run |
- // a given task spec before), and a score of 2.0. |
- for _, c := range s.queue { |
- assert.Equal(t, buildTask, c.Name) |
- assert.Equal(t, []string{c.Revision}, c.Commits) |
- assert.Equal(t, 2.0, c.Score) |
- } |
- |
- // Insert one of the tasks. |
- var t1 *db.Task |
- for _, c := range s.queue { // Order not guaranteed; find the right candidate. |
- if c.Revision == c1 { |
- t1 = makeTask(c.Name, c.Repo, c.Revision) |
- break |
- } |
- } |
- assert.NotNil(t, t1) |
- t1.Status = db.TASK_STATUS_SUCCESS |
- t1.IsolatedOutput = "fake isolated hash" |
- assert.NoError(t, d.PutTask(t1)) |
- assert.NoError(t, cache.Update()) |
- |
- // Regenerate the task queue. |
- assert.NoError(t, s.regenerateTaskQueue()) |
- |
- // Now we expect the queue to contain the other Build task and the one |
- // Test task we unblocked by running the first Build task. |
- assert.Equal(t, 2, len(s.queue)) |
- testSort() |
- for _, c := range s.queue { |
- assert.Equal(t, 2.0, c.Score) |
- assert.Equal(t, []string{c.Revision}, c.Commits) |
- } |
- buildIdx := 0 |
- testIdx := 1 |
- if s.queue[1].Name == buildTask { |
- buildIdx = 1 |
- testIdx = 0 |
- } |
- assert.Equal(t, buildTask, s.queue[buildIdx].Name) |
- assert.Equal(t, c2, s.queue[buildIdx].Revision) |
- |
- assert.Equal(t, testTask, s.queue[testIdx].Name) |
- assert.Equal(t, c1, s.queue[testIdx].Revision) |
- |
- // Run the other Build task. |
- t2 := makeTask(s.queue[buildIdx].Name, s.queue[buildIdx].Repo, s.queue[buildIdx].Revision) |
- t2.Status = db.TASK_STATUS_SUCCESS |
- t2.IsolatedOutput = "fake isolated hash" |
- assert.NoError(t, d.PutTask(t2)) |
- assert.NoError(t, cache.Update()) |
- |
- // Regenerate the task queue. |
- assert.NoError(t, s.regenerateTaskQueue()) |
- assert.Equal(t, 3, len(s.queue)) |
- testSort() |
- perfIdx := -1 |
- for i, c := range s.queue { |
- if c.Name == perfTask { |
- perfIdx = i |
- } else { |
- assert.Equal(t, c.Name, testTask) |
- } |
- assert.Equal(t, 2.0, c.Score) |
- assert.Equal(t, []string{c.Revision}, c.Commits) |
- } |
- assert.True(t, perfIdx > -1) |
- |
- // Run the Test task at tip of tree, but make its blamelist cover both |
- // commits. |
- t3 := makeTask(testTask, repoName, c2) |
- t3.Commits = append(t3.Commits, c1) |
- t3.Status = db.TASK_STATUS_SUCCESS |
- t3.IsolatedOutput = "fake isolated hash" |
- assert.NoError(t, d.PutTask(t3)) |
- assert.NoError(t, cache.Update()) |
- |
- // Regenerate the task queue. |
- assert.NoError(t, s.regenerateTaskQueue()) |
- |
- // Now we expect the queue to contain one Test and one Perf task. The |
- // Test task is a backfill, and should have a score of 0.5. |
- assert.Equal(t, 2, len(s.queue)) |
- testSort() |
- // First candidate should be the perf task. |
- assert.Equal(t, perfTask, s.queue[0].Name) |
- assert.Equal(t, 2.0, s.queue[0].Score) |
- // The test task is next, a backfill. |
- assert.Equal(t, testTask, s.queue[1].Name) |
- assert.Equal(t, 0.5, s.queue[1].Score) |
-} |
- |
-func makeTaskCandidate(name string, dims []string) *taskCandidate { |
- return &taskCandidate{ |
- Name: name, |
- TaskSpec: &TaskSpec{ |
- Dimensions: dims, |
- }, |
- } |
-} |
- |
-func makeSwarmingBot(id string, dims []string) *swarming_api.SwarmingRpcsBotInfo { |
- d := make([]*swarming_api.SwarmingRpcsStringListPair, 0, len(dims)) |
- for _, s := range dims { |
- split := strings.SplitN(s, ":", 2) |
- d = append(d, &swarming_api.SwarmingRpcsStringListPair{ |
- Key: split[0], |
- Value: []string{split[1]}, |
- }) |
- } |
- return &swarming_api.SwarmingRpcsBotInfo{ |
- BotId: id, |
- Dimensions: d, |
- } |
-} |
- |
-func TestGetCandidatesToSchedule(t *testing.T) { |
- // Empty lists. |
- rv := getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{}, []*taskCandidate{}) |
- assert.Equal(t, 0, len(rv)) |
- |
- t1 := makeTaskCandidate("task1", []string{"k:v"}) |
- rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{}, []*taskCandidate{t1}) |
- assert.Equal(t, 0, len(rv)) |
- |
- b1 := makeSwarmingBot("bot1", []string{"k:v"}) |
- rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1}, []*taskCandidate{}) |
- assert.Equal(t, 0, len(rv)) |
- |
- // Single match. |
- rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1}, []*taskCandidate{t1}) |
- testutils.AssertDeepEqual(t, []*taskCandidate{t1}, rv) |
- |
- // No match. |
- t1.TaskSpec.Dimensions[0] = "k:v2" |
- rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1}, []*taskCandidate{t1}) |
- assert.Equal(t, 0, len(rv)) |
- |
- // Add a task candidate to match b1. |
- t1 = makeTaskCandidate("task1", []string{"k:v2"}) |
- t2 := makeTaskCandidate("task2", []string{"k:v"}) |
- rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1}, []*taskCandidate{t1, t2}) |
- testutils.AssertDeepEqual(t, []*taskCandidate{t2}, rv) |
- |
- // Switch the task order. |
- t1 = makeTaskCandidate("task1", []string{"k:v2"}) |
- t2 = makeTaskCandidate("task2", []string{"k:v"}) |
- rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1}, []*taskCandidate{t2, t1}) |
- testutils.AssertDeepEqual(t, []*taskCandidate{t2}, rv) |
- |
- // Make both tasks match the bot, ensure that we pick the first one. |
- t1 = makeTaskCandidate("task1", []string{"k:v"}) |
- t2 = makeTaskCandidate("task2", []string{"k:v"}) |
- rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1}, []*taskCandidate{t1, t2}) |
- testutils.AssertDeepEqual(t, []*taskCandidate{t1}, rv) |
- rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1}, []*taskCandidate{t2, t1}) |
- testutils.AssertDeepEqual(t, []*taskCandidate{t2}, rv) |
- |
- // Multiple dimensions. Ensure that different permutations of the bots |
- // and tasks lists give us the expected results. |
- dims := []string{"k:v", "k2:v2", "k3:v3"} |
- b1 = makeSwarmingBot("bot1", dims) |
- b2 := makeSwarmingBot("bot2", t1.TaskSpec.Dimensions) |
- t1 = makeTaskCandidate("task1", []string{"k:v"}) |
- t2 = makeTaskCandidate("task2", dims) |
- // In the first two cases, the task with fewer dimensions has the |
- // higher priority. It gets the bot with more dimensions because it |
- // is first in sorted order. The second task does not get scheduled |
- // because there is no bot available which can run it. |
- // TODO(borenet): Use a more optimal solution to avoid this case. |
- rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1, b2}, []*taskCandidate{t1, t2}) |
- testutils.AssertDeepEqual(t, []*taskCandidate{t1}, rv) |
- t1 = makeTaskCandidate("task1", []string{"k:v"}) |
- t2 = makeTaskCandidate("task2", dims) |
- rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b2, b1}, []*taskCandidate{t1, t2}) |
- testutils.AssertDeepEqual(t, []*taskCandidate{t1}, rv) |
- // In these two cases, the task with more dimensions has the higher |
- // priority. Both tasks get scheduled. |
- t1 = makeTaskCandidate("task1", []string{"k:v"}) |
- t2 = makeTaskCandidate("task2", dims) |
- rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1, b2}, []*taskCandidate{t2, t1}) |
- testutils.AssertDeepEqual(t, []*taskCandidate{t2, t1}, rv) |
- t1 = makeTaskCandidate("task1", []string{"k:v"}) |
- t2 = makeTaskCandidate("task2", dims) |
- rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b2, b1}, []*taskCandidate{t2, t1}) |
- testutils.AssertDeepEqual(t, []*taskCandidate{t2, t1}, rv) |
- |
- // Matching dimensions. More bots than tasks. |
- b2 = makeSwarmingBot("bot2", dims) |
- b3 := makeSwarmingBot("bot3", dims) |
- t1 = makeTaskCandidate("task1", dims) |
- t2 = makeTaskCandidate("task2", dims) |
- t3 := makeTaskCandidate("task3", dims) |
- rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1, b2, b3}, []*taskCandidate{t1, t2}) |
- testutils.AssertDeepEqual(t, []*taskCandidate{t1, t2}, rv) |
- |
- // More tasks than bots. |
- t1 = makeTaskCandidate("task1", dims) |
- t2 = makeTaskCandidate("task2", dims) |
- t3 = makeTaskCandidate("task3", dims) |
- rv = getCandidatesToSchedule([]*swarming_api.SwarmingRpcsBotInfo{b1, b2}, []*taskCandidate{t1, t2, t3}) |
- testutils.AssertDeepEqual(t, []*taskCandidate{t1, t2}, rv) |
-} |
- |
-func makeBot(id string, dims map[string]string) *swarming_api.SwarmingRpcsBotInfo { |
- dimensions := make([]*swarming_api.SwarmingRpcsStringListPair, 0, len(dims)) |
- for k, v := range dims { |
- dimensions = append(dimensions, &swarming_api.SwarmingRpcsStringListPair{ |
- Key: k, |
- Value: []string{v}, |
- }) |
- } |
- return &swarming_api.SwarmingRpcsBotInfo{ |
- BotId: id, |
- Dimensions: dimensions, |
- } |
-} |
- |
-func TestSchedulingE2E(t *testing.T) { |
- testutils.SkipIfShort(t) |
- |
- // Setup. |
- tr := util.NewTempRepo() |
- defer tr.Cleanup() |
- d := db.NewInMemoryDB() |
- cache, err := db.NewTaskCache(d, time.Hour) |
- assert.NoError(t, err) |
- |
- // The test repo has two commits. The first commit adds a tasks.cfg file |
- // with two task specs: a build task and a test task, the test task |
- // depending on the build task. The second commit adds a perf task spec, |
- // which also depends on the build task. Therefore, there are five total |
- // possible tasks we could run: |
- // |
- // Build@c1, Test@c1, Build@c2, Test@c2, Perf@c2 |
- // |
- c1 := "c06ac6093d3029dffe997e9d85e8e61fee5f87b9" |
- c2 := "0f87799ac791b8d8573e93694d05b05a65e09668" |
- |
- repoName := "skia.git" |
- |
- isolateClient, err := isolate.NewClient(tr.Dir) |
- assert.NoError(t, err) |
- isolateClient.ServerUrl = isolate.FAKE_SERVER_URL |
- swarmingClient := swarming.NewTestClient() |
- s, err := NewTaskScheduler(d, cache, time.Duration(math.MaxInt64), tr.Dir, []string{repoName}, isolateClient, swarmingClient) |
- |
- // Start testing. No free bots, so we get a full queue with nothing |
- // scheduled. |
- assert.NoError(t, s.MainLoop()) |
- tasks, err := cache.GetTasksForCommits(repoName, []string{c1, c2}) |
- assert.NoError(t, err) |
- expect := map[string]map[string]*db.Task{ |
- c1: map[string]*db.Task{}, |
- c2: map[string]*db.Task{}, |
- } |
- testutils.AssertDeepEqual(t, expect, tasks) |
- |
- // A bot is free but doesn't have all of the right dimensions to run a task. |
- bot1 := makeBot("bot1", map[string]string{"pool": "Skia"}) |
- swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1}) |
- assert.NoError(t, s.MainLoop()) |
- tasks, err = cache.GetTasksForCommits(repoName, []string{c1, c2}) |
- assert.NoError(t, err) |
- expect = map[string]map[string]*db.Task{ |
- c1: map[string]*db.Task{}, |
- c2: map[string]*db.Task{}, |
- } |
- testutils.AssertDeepEqual(t, expect, tasks) |
- assert.Equal(t, 2, len(s.queue)) |
- |
- // One bot free, schedule a task, ensure it's not in the queue. |
- bot1.Dimensions = append(bot1.Dimensions, &swarming_api.SwarmingRpcsStringListPair{ |
- Key: "os", |
- Value: []string{"Ubuntu"}, |
- }) |
- swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1}) |
- assert.NoError(t, s.MainLoop()) |
- tasks, err = cache.GetTasksForCommits(repoName, []string{c1, c2}) |
- assert.NoError(t, err) |
- var t1 *db.Task |
- for _, v := range tasks { |
- for _, t := range v { |
- t1 = t |
- break |
- } |
- } |
- assert.NotNil(t, t1) |
- assert.Equal(t, 1, len(s.queue)) |
- |
- // The task is complete. |
- t1.Status = db.TASK_STATUS_SUCCESS |
- t1.Finished = time.Now() |
- t1.IsolatedOutput = "abc123" |
- assert.NoError(t, d.PutTask(t1)) |
- swarmingClient.MockTasks([]*swarming_api.SwarmingRpcsTaskRequestMetadata{ |
- makeSwarmingRpcsTaskRequestMetadata(t, t1), |
- }) |
- |
- // No bots free. Ensure that the queue is correct. |
- swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{}) |
- assert.NoError(t, s.MainLoop()) |
- tasks, err = cache.GetTasksForCommits(repoName, []string{c1, c2}) |
- assert.NoError(t, err) |
- // The tests don't use any time-based score scaling, because the commits |
- // in the test repo have fixed timestamps and would eventually result in |
- // zero scores. The side effect is that we don't know which of c1 or c2 |
- // will be chosen because they end up with the same score. |
- expectLen := 2 // One remaining build task, plus one test task. |
- if t1.Revision == c2 { |
- expectLen = 3 // c2 adds a perf task. |
- } |
- assert.Equal(t, expectLen, len(s.queue)) |
- |
- // More bots than tasks free, ensure the queue is correct. |
- bot2 := makeBot("bot2", map[string]string{ |
- "pool": "Skia", |
- "os": "Android", |
- "device_type": "grouper", |
- }) |
- bot3 := makeBot("bot3", map[string]string{ |
- "pool": "Skia", |
- "os": "Android", |
- "device_type": "grouper", |
- }) |
- bot4 := makeBot("bot4", map[string]string{ |
- "pool": "Skia", |
- "os": "Ubuntu", |
- }) |
- swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1, bot2, bot3, bot4}) |
- assert.NoError(t, s.MainLoop()) |
- _, err = cache.GetTasksForCommits(repoName, []string{c1, c2}) |
- assert.NoError(t, err) |
- assert.Equal(t, 0, len(s.queue)) |
- |
- // Second compile task finished. |
- var t2 *db.Task |
- var t3 *db.Task |
- var t4 *db.Task |
- tasks, err = cache.GetTasksForCommits(repoName, []string{c1, c2}) |
- assert.NoError(t, err) |
- for _, v := range tasks { |
- for _, task := range v { |
- if task.Name == t1.Name { |
- if (t1.Revision == c1 && task.Revision == c2) || (t1.Revision == c2 && task.Revision == c1) { |
- t2 = task |
- } |
- } else { |
- if t3 == nil { |
- t3 = task |
- } else { |
- t4 = task |
- } |
- } |
- } |
- } |
- assert.NotNil(t, t2) |
- assert.NotNil(t, t3) |
- t2.Status = db.TASK_STATUS_SUCCESS |
- t2.Finished = time.Now() |
- t2.IsolatedOutput = "abc123" |
- |
- // No new bots free; ensure that the newly-available tasks are in the queue. |
- swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{}) |
- mockTasks := []*swarming_api.SwarmingRpcsTaskRequestMetadata{ |
- makeSwarmingRpcsTaskRequestMetadata(t, t2), |
- makeSwarmingRpcsTaskRequestMetadata(t, t3), |
- } |
- if t4 != nil { |
- mockTasks = append(mockTasks, makeSwarmingRpcsTaskRequestMetadata(t, t4)) |
- } |
- swarmingClient.MockTasks(mockTasks) |
- assert.NoError(t, s.MainLoop()) |
- expectLen = 1 // Test task from c1 |
- if t2.Revision == c2 { |
- expectLen = 2 // Test and perf tasks from c2 |
- } |
- assert.Equal(t, expectLen, len(s.queue)) |
- |
- // Finish the other tasks. |
- t3, err = cache.GetTask(t3.Id) |
- assert.NoError(t, err) |
- t3.Status = db.TASK_STATUS_SUCCESS |
- t3.Finished = time.Now() |
- t3.IsolatedOutput = "abc123" |
- if t4 != nil { |
- t4, err = cache.GetTask(t4.Id) |
- assert.NoError(t, err) |
- t4.Status = db.TASK_STATUS_SUCCESS |
- t4.Finished = time.Now() |
- t4.IsolatedOutput = "abc123" |
- } |
- |
- // Ensure that we finally run all of the tasks and insert into the DB. |
- swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1, bot2, bot3, bot4}) |
- mockTasks = []*swarming_api.SwarmingRpcsTaskRequestMetadata{ |
- makeSwarmingRpcsTaskRequestMetadata(t, t3), |
- } |
- if t4 != nil { |
- mockTasks = append(mockTasks, makeSwarmingRpcsTaskRequestMetadata(t, t4)) |
- } |
- assert.NoError(t, s.MainLoop()) |
- tasks, err = cache.GetTasksForCommits(repoName, []string{c1, c2}) |
- assert.NoError(t, err) |
- assert.Equal(t, 2, len(tasks[c1])) |
- assert.Equal(t, 3, len(tasks[c2])) |
- assert.Equal(t, 0, len(s.queue)) |
- |
- // Mark everything as finished. Ensure that the queue still ends up empty. |
- tasksList := []*db.Task{} |
- for _, v := range tasks { |
- for _, task := range v { |
- if task.Status != db.TASK_STATUS_SUCCESS { |
- task.Status = db.TASK_STATUS_SUCCESS |
- task.Finished = time.Now() |
- task.IsolatedOutput = "abc123" |
- tasksList = append(tasksList, task) |
- } |
- } |
- } |
- mockTasks = make([]*swarming_api.SwarmingRpcsTaskRequestMetadata, 0, len(tasksList)) |
- for _, task := range tasksList { |
- mockTasks = append(mockTasks, makeSwarmingRpcsTaskRequestMetadata(t, task)) |
- } |
- swarmingClient.MockTasks(mockTasks) |
- swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1, bot2, bot3, bot4}) |
- assert.NoError(t, s.MainLoop()) |
- assert.Equal(t, 0, len(s.queue)) |
-} |
- |
-func makeDummyCommits(t *testing.T, repoDir string, numCommits int) { |
- _, err := exec.RunCwd(repoDir, "git", "config", "user.email", "test@skia.org") |
- assert.NoError(t, err) |
- _, err = exec.RunCwd(repoDir, "git", "config", "user.name", "Skia Tester") |
- assert.NoError(t, err) |
- _, err = exec.RunCwd(repoDir, "git", "checkout", "master") |
- assert.NoError(t, err) |
- dummyFile := path.Join(repoDir, "dummyfile.txt") |
- for i := 0; i < numCommits; i++ { |
- title := fmt.Sprintf("Dummy #%d", i) |
- assert.NoError(t, ioutil.WriteFile(dummyFile, []byte(title), os.ModePerm)) |
- _, err = exec.RunCwd(repoDir, "git", "add", dummyFile) |
- assert.NoError(t, err) |
- _, err = exec.RunCwd(repoDir, "git", "commit", "-m", title) |
- assert.NoError(t, err) |
- _, err = exec.RunCwd(repoDir, "git", "push", "origin", "master") |
- assert.NoError(t, err) |
- } |
-} |
- |
-func TestSchedulerStealingFrom(t *testing.T) { |
- testutils.SkipIfShort(t) |
- |
- // Setup. |
- tr := util.NewTempRepo() |
- d := db.NewInMemoryDB() |
- cache, err := db.NewTaskCache(d, time.Hour) |
- assert.NoError(t, err) |
- |
- // The test repo has two commits. The first commit adds a tasks.cfg file |
- // with two task specs: a build task and a test task, the test task |
- // depending on the build task. The second commit adds a perf task spec, |
- // which also depends on the build task. Therefore, there are five total |
- // possible tasks we could run: |
- // |
- // Build@c1, Test@c1, Build@c2, Test@c2, Perf@c2 |
- // |
- c1 := "c06ac6093d3029dffe997e9d85e8e61fee5f87b9" |
- c2 := "0f87799ac791b8d8573e93694d05b05a65e09668" |
- buildTask := "Build-Ubuntu-GCC-Arm7-Release-Android" |
- repoName := "skia.git" |
- repoDir := path.Join(tr.Dir, repoName) |
- |
- repos := gitinfo.NewRepoMap(tr.Dir) |
- repo, err := repos.Repo(repoName) |
- assert.NoError(t, err) |
- isolateClient, err := isolate.NewClient(tr.Dir) |
- assert.NoError(t, err) |
- isolateClient.ServerUrl = isolate.FAKE_SERVER_URL |
- swarmingClient := swarming.NewTestClient() |
- s, err := NewTaskScheduler(d, cache, time.Duration(math.MaxInt64), tr.Dir, []string{"skia.git"}, isolateClient, swarmingClient) |
- assert.NoError(t, err) |
- |
- // Run both available compile tasks. |
- bot1 := makeBot("bot1", map[string]string{"pool": "Skia", "os": "Ubuntu"}) |
- bot2 := makeBot("bot2", map[string]string{"pool": "Skia", "os": "Ubuntu"}) |
- swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1, bot2}) |
- assert.NoError(t, s.MainLoop()) |
- tasks, err := cache.GetTasksForCommits(repoName, []string{c1, c2}) |
- assert.NoError(t, err) |
- assert.Equal(t, 1, len(tasks[c1])) |
- assert.Equal(t, 1, len(tasks[c2])) |
- tasksList := []*db.Task{} |
- for _, v := range tasks { |
- for _, task := range v { |
- if task.Status != db.TASK_STATUS_SUCCESS { |
- task.Status = db.TASK_STATUS_SUCCESS |
- task.Finished = time.Now() |
- task.IsolatedOutput = "abc123" |
- tasksList = append(tasksList, task) |
- } |
- } |
- } |
- assert.NoError(t, d.PutTasks(tasksList)) |
- assert.NoError(t, cache.Update()) |
- |
- // Add some commits. |
- makeDummyCommits(t, repoDir, 10) |
- commits, err := repo.RevList("HEAD") |
- assert.NoError(t, err) |
- |
- // Run one task. Ensure that it's at tip-of-tree. |
- head, err := repo.FullHash("HEAD") |
- assert.NoError(t, err) |
- swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1}) |
- assert.NoError(t, s.MainLoop()) |
- tasks, err = cache.GetTasksForCommits(repoName, commits) |
- assert.NoError(t, err) |
- assert.Equal(t, 1, len(tasks[head])) |
- task := tasks[head][buildTask] |
- assert.Equal(t, head, task.Revision) |
- expect := commits[:len(commits)-2] |
- sort.Strings(expect) |
- sort.Strings(task.Commits) |
- testutils.AssertDeepEqual(t, expect, task.Commits) |
- |
- task.Status = db.TASK_STATUS_SUCCESS |
- task.Finished = time.Now() |
- task.IsolatedOutput = "abc123" |
- assert.NoError(t, d.PutTask(task)) |
- assert.NoError(t, cache.Update()) |
- |
- oldTasksByCommit := tasks |
- |
- // Run backfills, ensuring that each one steals the right set of commits |
- // from previous builds, until all of the build task candidates have run. |
- for i := 0; i < 9; i++ { |
- // Now, run another task. The new task should bisect the old one. |
- swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1}) |
- assert.NoError(t, s.MainLoop()) |
- tasks, err = cache.GetTasksForCommits(repoName, commits) |
- assert.NoError(t, err) |
- var newTask *db.Task |
- for _, v := range tasks { |
- for _, task := range v { |
- if task.Status == db.TASK_STATUS_PENDING { |
- assert.True(t, newTask == nil || task.Id == newTask.Id) |
- newTask = task |
- } |
- } |
- } |
- assert.NotNil(t, newTask) |
- |
- oldTask := oldTasksByCommit[newTask.Revision][newTask.Name] |
- assert.NotNil(t, oldTask) |
- assert.True(t, util.In(newTask.Revision, oldTask.Commits)) |
- |
- // Find the updated old task. |
- updatedOldTask, err := cache.GetTask(oldTask.Id) |
- assert.NoError(t, err) |
- assert.NotNil(t, updatedOldTask) |
- |
- // Ensure that the blamelists are correct. |
- old := util.NewStringSet(oldTask.Commits) |
- new := util.NewStringSet(newTask.Commits) |
- updatedOld := util.NewStringSet(updatedOldTask.Commits) |
- |
- testutils.AssertDeepEqual(t, old, new.Union(updatedOld)) |
- assert.Equal(t, 0, len(new.Intersect(updatedOld))) |
- // Finish the new task. |
- newTask.Status = db.TASK_STATUS_SUCCESS |
- newTask.Finished = time.Now() |
- newTask.IsolatedOutput = "abc123" |
- assert.NoError(t, d.PutTask(newTask)) |
- assert.NoError(t, cache.Update()) |
- oldTasksByCommit = tasks |
- |
- } |
- |
- // Ensure that we're really done. |
- swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1}) |
- assert.NoError(t, s.MainLoop()) |
- tasks, err = cache.GetTasksForCommits(repoName, commits) |
- assert.NoError(t, err) |
- var newTask *db.Task |
- for _, v := range tasks { |
- for _, task := range v { |
- if task.Status == db.TASK_STATUS_PENDING { |
- assert.True(t, newTask == nil || task.Id == newTask.Id) |
- newTask = task |
- } |
- } |
- } |
- assert.Nil(t, newTask) |
-} |
- |
-func TestMultipleCandidatesBackfillingEachOther(t *testing.T) { |
- testutils.SkipIfShort(t) |
- |
- workdir, err := ioutil.TempDir("", "") |
- assert.NoError(t, err) |
- defer testutils.RemoveAll(t, workdir) |
- |
- run := func(dir string, cmd ...string) { |
- _, err := exec.RunCwd(dir, cmd...) |
- assert.NoError(t, err) |
- } |
- |
- addFile := func(repoDir, subPath, contents string) { |
- assert.NoError(t, ioutil.WriteFile(path.Join(repoDir, subPath), []byte(contents), os.ModePerm)) |
- run(repoDir, "git", "add", subPath) |
- } |
- |
- repoName := "skia.git" |
- repoDir := path.Join(workdir, repoName) |
- |
- assert.NoError(t, ioutil.WriteFile(path.Join(workdir, ".gclient"), []byte("dummy"), os.ModePerm)) |
- |
- assert.NoError(t, os.Mkdir(path.Join(workdir, repoName), os.ModePerm)) |
- run(repoDir, "git", "init") |
- run(repoDir, "git", "remote", "add", "origin", ".") |
- |
- infraBotsSubDir := path.Join("infra", "bots") |
- infraBotsDir := path.Join(repoDir, infraBotsSubDir) |
- assert.NoError(t, os.MkdirAll(infraBotsDir, os.ModePerm)) |
- |
- addFile(repoDir, "somefile.txt", "dummy3") |
- addFile(repoDir, path.Join(infraBotsSubDir, "dummy.isolate"), `{ |
- 'variables': { |
- 'command': [ |
- 'python', 'recipes.py', 'run', |
- ], |
- 'files': [ |
- '../../somefile.txt', |
- ], |
- }, |
-}`) |
- |
- // Create a single task in the config. |
- taskName := "dummytask" |
- cfg := &TasksCfg{ |
- Tasks: map[string]*TaskSpec{ |
- taskName: &TaskSpec{ |
- CipdPackages: []*CipdPackage{}, |
- Dependencies: []string{}, |
- Dimensions: []string{"pool:Skia"}, |
- Isolate: "dummy.isolate", |
- Priority: 1.0, |
- }, |
- }, |
- } |
- f, err := os.Create(path.Join(repoDir, TASKS_CFG_FILE)) |
- assert.NoError(t, err) |
- assert.NoError(t, json.NewEncoder(f).Encode(&cfg)) |
- assert.NoError(t, f.Close()) |
- run(repoDir, "git", "add", TASKS_CFG_FILE) |
- run(repoDir, "git", "commit", "-m", "Add more tasks!") |
- run(repoDir, "git", "push", "origin", "master") |
- run(repoDir, "git", "branch", "-u", "origin/master") |
- |
- // Setup the scheduler. |
- repos := gitinfo.NewRepoMap(workdir) |
- repo, err := repos.Repo(repoName) |
- assert.NoError(t, err) |
- d := db.NewInMemoryDB() |
- cache, err := db.NewTaskCache(d, time.Hour) |
- assert.NoError(t, err) |
- isolateClient, err := isolate.NewClient(workdir) |
- assert.NoError(t, err) |
- isolateClient.ServerUrl = isolate.FAKE_SERVER_URL |
- swarmingClient := swarming.NewTestClient() |
- s, err := NewTaskScheduler(d, cache, time.Duration(math.MaxInt64), workdir, []string{repoName}, isolateClient, swarmingClient) |
- assert.NoError(t, err) |
- |
- mockTasks := []*swarming_api.SwarmingRpcsTaskRequestMetadata{} |
- mock := func(task *db.Task) { |
- mockTasks = append(mockTasks, makeSwarmingRpcsTaskRequestMetadata(t, task)) |
- swarmingClient.MockTasks(mockTasks) |
- } |
- |
- // Cycle once. |
- bot1 := makeBot("bot1", map[string]string{"pool": "Skia"}) |
- swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1}) |
- assert.NoError(t, s.MainLoop()) |
- assert.Equal(t, 0, len(s.queue)) |
- head, err := repo.FullHash("HEAD") |
- assert.NoError(t, err) |
- tasks, err := cache.GetTasksForCommits(repoName, []string{head}) |
- assert.NoError(t, err) |
- assert.Equal(t, 1, len(tasks[head])) |
- mock(tasks[head][taskName]) |
- |
- // Add some commits to the repo. |
- makeDummyCommits(t, repoDir, 8) |
- commits, err := repo.RevList(fmt.Sprintf("%s..HEAD", head)) |
- assert.Nil(t, err) |
- assert.Equal(t, 8, len(commits)) |
- |
- // Trigger builds simultaneously. |
- bot2 := makeBot("bot2", map[string]string{"pool": "Skia"}) |
- bot3 := makeBot("bot3", map[string]string{"pool": "Skia"}) |
- swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1, bot2, bot3}) |
- assert.NoError(t, s.MainLoop()) |
- assert.Equal(t, 5, len(s.queue)) |
- tasks, err = cache.GetTasksForCommits(repoName, commits) |
- assert.NoError(t, err) |
- |
- // If we're queueing correctly, we should've triggered tasks at |
- // commits[0], commits[4], and either commits[2] or commits[6]. |
- var t1, t2, t3 *db.Task |
- for _, byName := range tasks { |
- for _, task := range byName { |
- if task.Revision == commits[0] { |
- t1 = task |
- } else if task.Revision == commits[4] { |
- t2 = task |
- } else if task.Revision == commits[2] || task.Revision == commits[6] { |
- t3 = task |
- } else { |
- assert.FailNow(t, fmt.Sprintf("Task has unknown revision: %v", task)) |
- } |
- } |
- } |
- assert.NotNil(t, t1) |
- assert.NotNil(t, t2) |
- assert.NotNil(t, t3) |
- mock(t1) |
- mock(t2) |
- mock(t3) |
- |
- // Ensure that we got the blamelists right. |
- mkCopy := func(orig []string) []string { |
- rv := make([]string, len(orig)) |
- copy(rv, orig) |
- return rv |
- } |
- var expect1, expect2, expect3 []string |
- if t3.Revision == commits[2] { |
- expect1 = mkCopy(commits[:2]) |
- expect2 = mkCopy(commits[4:]) |
- expect3 = mkCopy(commits[2:4]) |
- } else { |
- expect1 = mkCopy(commits[:4]) |
- expect2 = mkCopy(commits[4:6]) |
- expect3 = mkCopy(commits[6:]) |
- } |
- sort.Strings(expect1) |
- sort.Strings(expect2) |
- sort.Strings(expect3) |
- sort.Strings(t1.Commits) |
- sort.Strings(t2.Commits) |
- sort.Strings(t3.Commits) |
- testutils.AssertDeepEqual(t, expect1, t1.Commits) |
- testutils.AssertDeepEqual(t, expect2, t2.Commits) |
- testutils.AssertDeepEqual(t, expect3, t3.Commits) |
- |
- // Just for good measure, check the task at the head of the queue. |
- expectIdx := 2 |
- if t3.Revision == commits[expectIdx] { |
- expectIdx = 6 |
- } |
- assert.Equal(t, commits[expectIdx], s.queue[0].Revision) |
- |
- // Run again with 5 bots to check the case where we bisect the same |
- // task twice. |
- bot4 := makeBot("bot4", map[string]string{"pool": "Skia"}) |
- bot5 := makeBot("bot5", map[string]string{"pool": "Skia"}) |
- swarmingClient.MockBots([]*swarming_api.SwarmingRpcsBotInfo{bot1, bot2, bot3, bot4, bot5}) |
- assert.NoError(t, s.MainLoop()) |
- assert.Equal(t, 0, len(s.queue)) |
- tasks, err = cache.GetTasksForCommits(repoName, commits) |
- assert.NoError(t, err) |
- for _, byName := range tasks { |
- for _, task := range byName { |
- assert.Equal(t, 1, len(task.Commits)) |
- } |
- } |
-} |