// Copyright 2017 syzkaller project authors. All rights reserved. // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. // +build aetest package dash import ( "testing" "time" "github.com/google/syzkaller/dashboard/dashapi" ) // Basic scenario of marking a bug as fixed by a particular commit, // discovering this commit on builder and marking the bug as ultimately fixed. func TestFixBasic(t *testing.T) { c := NewCtx(t) defer c.Close() build1 := testBuild(1) c.client.UploadBuild(build1) crash1 := testCrash(build1, 1) c.client.ReportCrash(crash1) builderPollResp, _ := c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 0) needRepro, _ := c.client.NeedRepro(testCrashID(crash1)) c.expectEQ(needRepro, true) rep := c.client.pollBug() // Specify fixing commit for the bug. reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ ID: rep.ID, Status: dashapi.BugStatusOpen, FixCommits: []string{"foo: fix the crash"}, }) c.expectEQ(reply.OK, true) // Don't need repro once there are fixing commits. needRepro, _ = c.client.NeedRepro(testCrashID(crash1)) c.expectEQ(needRepro, false) // Check that the commit is now passed to builders. builderPollResp, _ = c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 1) c.expectEQ(builderPollResp.PendingCommits[0], "foo: fix the crash") // Patches must not be reset on other actions. c.client.updateBug(rep.ID, dashapi.BugStatusOpen, "") // Upstream commands must fail if patches are already present. // Right course of action is unclear in this situation, // so this test merely documents the current behavior. reply, _ = c.client.ReportingUpdate(&dashapi.BugUpdate{ ID: rep.ID, Status: dashapi.BugStatusUpstream, }) c.expectEQ(reply.OK, false) c.client.ReportCrash(crash1) c.client.pollBugs(0) // Upload another build with the commit present. build2 := testBuild(2) build2.Manager = build1.Manager build2.Commits = []string{"foo: fix the crash"} c.client.UploadBuild(build2) // Check that the commit is now not passed to this builder. builderPollResp, _ = c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 0) // Ensure that a new crash creates a new bug (the old one must be marked as fixed). c.client.ReportCrash(crash1) rep2 := c.client.pollBug() c.expectEQ(rep2.Title, "title1 (2)") // Regression test: previously upstreamming failed because the new bug had fixing commits. c.client.ReportCrash(crash1) c.client.updateBug(rep2.ID, dashapi.BugStatusUpstream, "") } // Test bug that is fixed by 2 commits. func TestFixedByTwoCommits(t *testing.T) { c := NewCtx(t) defer c.Close() build1 := testBuild(1) c.client.UploadBuild(build1) crash1 := testCrash(build1, 1) c.client.ReportCrash(crash1) builderPollResp, _ := c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 0) rep := c.client.pollBug() // Specify fixing commit for the bug. reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ ID: rep.ID, Status: dashapi.BugStatusOpen, FixCommits: []string{"bar: prepare for fixing", "\"foo: fix the crash\""}, }) c.expectEQ(reply.OK, true) // Check that the commit is now passed to builders. builderPollResp, _ = c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 2) c.expectEQ(builderPollResp.PendingCommits[0], "bar: prepare for fixing") c.expectEQ(builderPollResp.PendingCommits[1], "foo: fix the crash") // Upload another build with only one of the commits. build2 := testBuild(2) build2.Manager = build1.Manager build2.Commits = []string{"bar: prepare for fixing"} c.client.UploadBuild(build2) // Check that it has not fixed the bug. builderPollResp, _ = c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 2) c.expectEQ(builderPollResp.PendingCommits[0], "bar: prepare for fixing") c.expectEQ(builderPollResp.PendingCommits[1], "foo: fix the crash") c.client.ReportCrash(crash1) c.client.pollBugs(0) // Now upload build with both commits. build3 := testBuild(3) build3.Manager = build1.Manager build3.Commits = []string{"foo: fix the crash", "bar: prepare for fixing"} c.client.UploadBuild(build3) // Check that the commit is now not passed to this builder. builderPollResp, _ = c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 0) // Ensure that a new crash creates a new bug (the old one must be marked as fixed). c.client.ReportCrash(crash1) rep2 := c.client.pollBug() c.expectEQ(rep2.Title, "title1 (2)") } // A bug is marked as fixed by one commit and then remarked as fixed by another. func TestReFixed(t *testing.T) { c := NewCtx(t) defer c.Close() build1 := testBuild(1) c.client.UploadBuild(build1) crash1 := testCrash(build1, 1) c.client.ReportCrash(crash1) builderPollResp, _ := c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 0) rep := c.client.pollBug() // Specify fixing commit for the bug. reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ ID: rep.ID, Status: dashapi.BugStatusOpen, FixCommits: []string{"a wrong one"}, }) c.expectEQ(reply.OK, true) reply, _ = c.client.ReportingUpdate(&dashapi.BugUpdate{ ID: rep.ID, Status: dashapi.BugStatusOpen, FixCommits: []string{"the right one"}, }) c.expectEQ(reply.OK, true) builderPollResp, _ = c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 1) c.expectEQ(builderPollResp.PendingCommits[0], "the right one") // Upload another build with the wrong commit. build2 := testBuild(2) build2.Manager = build1.Manager build2.Commits = []string{"a wrong one"} c.client.UploadBuild(build2) // Check that it has not fixed the bug. builderPollResp, _ = c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 1) c.expectEQ(builderPollResp.PendingCommits[0], "the right one") c.client.ReportCrash(crash1) c.client.pollBugs(0) // Now upload build with the right commit. build3 := testBuild(3) build3.Manager = build1.Manager build3.Commits = []string{"the right one"} c.client.UploadBuild(build3) // Check that the commit is now not passed to this builder. builderPollResp, _ = c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 0) } // Fixing commit is present on one manager, but missing on another. func TestFixTwoManagers(t *testing.T) { c := NewCtx(t) defer c.Close() build1 := testBuild(1) c.client.UploadBuild(build1) crash1 := testCrash(build1, 1) c.client.ReportCrash(crash1) builderPollResp, _ := c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 0) rep := c.client.pollBug() // Specify fixing commit for the bug. reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ ID: rep.ID, Status: dashapi.BugStatusOpen, FixCommits: []string{"foo: fix the crash"}, }) c.expectEQ(reply.OK, true) // Now the second manager appears. build2 := testBuild(2) c.client.UploadBuild(build2) // Check that the commit is now passed to builders. builderPollResp, _ = c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 1) c.expectEQ(builderPollResp.PendingCommits[0], "foo: fix the crash") builderPollResp, _ = c.client.BuilderPoll(build2.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 1) c.expectEQ(builderPollResp.PendingCommits[0], "foo: fix the crash") // Now first manager picks up the commit. build3 := testBuild(3) build3.Manager = build1.Manager build3.Commits = []string{"foo: fix the crash"} c.client.UploadBuild(build3) // Check that the commit is now not passed to this builder. builderPollResp, _ = c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 0) // But still passed to another. builderPollResp, _ = c.client.BuilderPoll(build2.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 1) c.expectEQ(builderPollResp.PendingCommits[0], "foo: fix the crash") // Check that the bug is still open. c.client.ReportCrash(crash1) c.client.pollBugs(0) // Now the second manager picks up the commit. build4 := testBuild(4) build4.Manager = build2.Manager build4.Commits = []string{"foo: fix the crash"} c.client.UploadBuild(build4) // Now the bug must be fixed. builderPollResp, _ = c.client.BuilderPoll(build2.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 0) c.client.ReportCrash(crash1) rep2 := c.client.pollBug() c.expectEQ(rep2.Title, "title1 (2)") } func TestReFixedTwoManagers(t *testing.T) { c := NewCtx(t) defer c.Close() build1 := testBuild(1) c.client.UploadBuild(build1) crash1 := testCrash(build1, 1) c.client.ReportCrash(crash1) builderPollResp, _ := c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 0) rep := c.client.pollBug() // Specify fixing commit for the bug. reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ ID: rep.ID, Status: dashapi.BugStatusOpen, FixCommits: []string{"foo: fix the crash"}, }) c.expectEQ(reply.OK, true) // Now the second manager appears. build2 := testBuild(2) c.client.UploadBuild(build2) // Now first manager picks up the commit. build3 := testBuild(3) build3.Manager = build1.Manager build3.Commits = []string{"foo: fix the crash"} c.client.UploadBuild(build3) builderPollResp, _ = c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 0) // Now we change the fixing commit. reply, _ = c.client.ReportingUpdate(&dashapi.BugUpdate{ ID: rep.ID, Status: dashapi.BugStatusOpen, FixCommits: []string{"the right one"}, }) c.expectEQ(reply.OK, true) // Now it must again appear on both managers. builderPollResp, _ = c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 1) c.expectEQ(builderPollResp.PendingCommits[0], "the right one") builderPollResp, _ = c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 1) c.expectEQ(builderPollResp.PendingCommits[0], "the right one") // Now the second manager picks up the second commit. build4 := testBuild(4) build4.Manager = build2.Manager build4.Commits = []string{"the right one"} c.client.UploadBuild(build4) // The bug must be still open. c.client.ReportCrash(crash1) c.client.pollBugs(0) // Specify fixing commit again, but it's the same one as before, so nothing changed. reply, _ = c.client.ReportingUpdate(&dashapi.BugUpdate{ ID: rep.ID, Status: dashapi.BugStatusOpen, FixCommits: []string{"the right one"}, }) c.expectEQ(reply.OK, true) // Now the first manager picks up the second commit. build5 := testBuild(5) build5.Manager = build1.Manager build5.Commits = []string{"the right one"} c.client.UploadBuild(build5) // Now the bug must be fixed. builderPollResp, _ = c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 0) c.client.ReportCrash(crash1) rep2 := c.client.pollBug() c.expectEQ(rep2.Title, "title1 (2)") } // TestFixedWithCommitTags tests fixing of bugs with Reported-by commit tags. func TestFixedWithCommitTags(t *testing.T) { c := NewCtx(t) defer c.Close() build1 := testBuild(1) c.client.UploadBuild(build1) build2 := testBuild(2) c.client.UploadBuild(build2) crash1 := testCrash(build1, 1) c.client.ReportCrash(crash1) rep := c.client.pollBug() // Upload build with 2 fixing commits for this bug. build1.FixCommits = []dashapi.FixCommit{{"fix commit 1", rep.ID}, {"fix commit 2", rep.ID}} c.client.UploadBuild(build1) // Now the commits must be associated with the bug and the second // manager must get them as pending. builderPollResp, _ := c.client.BuilderPoll(build2.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 2) c.expectEQ(builderPollResp.PendingCommits[0], "fix commit 1") c.expectEQ(builderPollResp.PendingCommits[1], "fix commit 2") // The first manager must not get them. builderPollResp, _ = c.client.BuilderPoll(build1.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 0) // The bug is still not fixed. c.client.ReportCrash(crash1) c.client.pollBugs(0) // Now the second manager reports the same commits. // This must close the bug. build2.FixCommits = build1.FixCommits c.client.UploadBuild(build2) // Commits must not be passed to managers. builderPollResp, _ = c.client.BuilderPoll(build2.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 0) // Ensure that a new crash creates a new bug. c.client.ReportCrash(crash1) rep2 := c.client.pollBug() c.expectEQ(rep2.Title, "title1 (2)") } // TestFixedDup tests Reported-by commit tag that comes for a dup. // In such case we need to associate it with the canonical bugs. func TestFixedDup(t *testing.T) { c := NewCtx(t) defer c.Close() build := testBuild(1) c.client.UploadBuild(build) crash1 := testCrash(build, 1) c.client.ReportCrash(crash1) rep1 := c.client.pollBug() crash2 := testCrash(build, 2) c.client.ReportCrash(crash2) rep2 := c.client.pollBug() // rep2 is a dup of rep1. c.client.updateBug(rep2.ID, dashapi.BugStatusDup, rep1.ID) // Upload build that fixes rep2. build.FixCommits = []dashapi.FixCommit{{"fix commit 1", rep2.ID}} c.client.UploadBuild(build) // This must fix rep1. c.client.ReportCrash(crash1) rep3 := c.client.pollBug() c.expectEQ(rep3.Title, rep1.Title+" (2)") } // TestFixedDup2 tests Reported-by commit tag that comes for a dup. // Ensure that non-canonical bug gets fixing commit too. func TestFixedDup2(t *testing.T) { c := NewCtx(t) defer c.Close() build1 := testBuild(1) c.client.UploadBuild(build1) build2 := testBuild(2) c.client.UploadBuild(build2) crash1 := testCrash(build1, 1) c.client.ReportCrash(crash1) rep1 := c.client.pollBug() crash2 := testCrash(build1, 2) c.client.ReportCrash(crash2) rep2 := c.client.pollBug() // rep2 is a dup of rep1. c.client.updateBug(rep2.ID, dashapi.BugStatusDup, rep1.ID) // Upload build that fixes rep2. build1.FixCommits = []dashapi.FixCommit{{"fix commit 1", rep2.ID}} c.client.UploadBuild(build1) /* dbBug1, _, _ := c.loadBug(rep1.ID) t.Logf("BUG1: status=%v, commits: %+v, patched: %+v", dbBug1.Status, dbBug1.Commits, dbBug1.PatchedOn) dbBug2, _, _ := c.loadBug(rep2.ID) t.Logf("BUG2: status=%v, commits: %+v, patched: %+v", dbBug2.Status, dbBug2.Commits, dbBug2.PatchedOn) */ // Now undup the bugs. They are still unfixed as only 1 manager uploaded the commit. c.client.updateBug(rep2.ID, dashapi.BugStatusOpen, "") // Now the second manager reports the same commits. This must close both bugs. build2.FixCommits = build1.FixCommits c.client.UploadBuild(build2) c.client.pollBugs(0) c.advanceTime(24 * time.Hour) c.client.ReportCrash(crash1) rep3 := c.client.pollBug() c.expectEQ(rep3.Title, rep1.Title+" (2)") c.client.ReportCrash(crash2) rep4 := c.client.pollBug() c.expectEQ(rep4.Title, rep2.Title+" (2)") } // TestFixedDup3 tests Reported-by commit tag that comes for both dup and canonical bug. func TestFixedDup3(t *testing.T) { c := NewCtx(t) defer c.Close() build1 := testBuild(1) c.client.UploadBuild(build1) build2 := testBuild(2) c.client.UploadBuild(build2) crash1 := testCrash(build1, 1) c.client.ReportCrash(crash1) rep1 := c.client.pollBug() crash2 := testCrash(build1, 2) c.client.ReportCrash(crash2) rep2 := c.client.pollBug() // rep2 is a dup of rep1. c.client.updateBug(rep2.ID, dashapi.BugStatusDup, rep1.ID) // Upload builds that fix rep1 and rep2 with different commits. // This must fix rep1 eventually and we must not livelock in such scenario. build1.FixCommits = []dashapi.FixCommit{{"fix commit 1", rep1.ID}, {"fix commit 2", rep2.ID}} build2.FixCommits = build1.FixCommits c.client.UploadBuild(build1) c.client.UploadBuild(build2) c.client.UploadBuild(build1) c.client.UploadBuild(build2) c.client.ReportCrash(crash1) rep3 := c.client.pollBug() c.expectEQ(rep3.Title, rep1.Title+" (2)") }