Golang程序  |  532行  |  15.86 KB

// 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)")
}