blob: 4d13fd0d537a787a7756b83d7be44c292b4f588c [file] [log] [blame]
// 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"
)
func TestReportBug(t *testing.T) {
c := NewCtx(t)
defer c.Close()
build := testBuild(1)
c.client.UploadBuild(build)
crash1 := &dashapi.Crash{
BuildID: "build1",
Title: "title1",
Maintainers: []string{`"Foo Bar" <foo@bar.com>`, `bar@foo.com`},
Log: []byte("log1"),
Report: []byte("report1"),
}
c.client.ReportCrash(crash1)
// Must get no reports for "unknown" type.
resp, _ := c.client.ReportingPollBugs("unknown")
c.expectEQ(len(resp.Reports), 0)
// Must get a proper report for "test" type.
resp, _ = c.client.ReportingPollBugs("test")
c.expectEQ(len(resp.Reports), 1)
rep := resp.Reports[0]
if rep.ID == "" {
t.Fatalf("empty report ID")
}
_, dbCrash, dbBuild := c.loadBug(rep.ID)
want := &dashapi.BugReport{
Namespace: "test1",
Config: []byte(`{"Index":1}`),
ID: rep.ID,
First: true,
Title: "title1",
Maintainers: []string{"bar@foo.com", "foo@bar.com"},
CompilerID: "compiler1",
KernelRepo: "repo1",
KernelRepoAlias: "repo1/branch1",
KernelBranch: "branch1",
KernelCommit: "1111111111111111111111111111111111111111",
KernelCommitTitle: build.KernelCommitTitle,
KernelCommitDate: buildCommitDate,
KernelConfig: []byte("config1"),
KernelConfigLink: externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig),
Log: []byte("log1"),
LogLink: externalLink(c.ctx, textCrashLog, dbCrash.Log),
Report: []byte("report1"),
ReportLink: externalLink(c.ctx, textCrashReport, dbCrash.Report),
CrashID: rep.CrashID,
NumCrashes: 1,
HappenedOn: []string{"repo1/branch1"},
}
c.expectEQ(rep, want)
// Since we did not update bug status yet, should get the same report again.
c.expectEQ(c.client.pollBug(), want)
// Now add syz repro and check that we get another bug report.
crash1.ReproOpts = []byte("some opts")
crash1.ReproSyz = []byte("getpid()")
want.First = false
want.ReproSyz = []byte(syzReproPrefix + "#some opts\ngetpid()")
c.client.ReportCrash(crash1)
rep1 := c.client.pollBug()
if want.CrashID == rep1.CrashID {
t.Fatal("get the same CrashID for new crash")
}
_, dbCrash, _ = c.loadBug(rep.ID)
want.CrashID = rep1.CrashID
want.NumCrashes = 2
want.ReproSyzLink = externalLink(c.ctx, textReproSyz, dbCrash.ReproSyz)
want.LogLink = externalLink(c.ctx, textCrashLog, dbCrash.Log)
want.ReportLink = externalLink(c.ctx, textCrashReport, dbCrash.Report)
c.expectEQ(rep1, want)
reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{
ID: rep.ID,
Status: dashapi.BugStatusOpen,
ReproLevel: dashapi.ReproLevelSyz,
})
c.expectEQ(reply.OK, true)
// After bug update should not get the report again.
c.client.pollBugs(0)
// Now close the bug in the first reporting.
c.client.updateBug(rep.ID, dashapi.BugStatusUpstream, "")
// Check that bug updates for the first reporting fail now.
reply, _ = c.client.ReportingUpdate(&dashapi.BugUpdate{ID: rep.ID, Status: dashapi.BugStatusOpen})
c.expectEQ(reply.OK, false)
// Report another crash with syz repro for this bug,
// ensure that we still report the original crash in the next reporting.
// That's what we've upstreammed, it's bad to switch crashes without reason.
crash1.Report = []byte("report2")
c.client.ReportCrash(crash1)
// Check that we get the report in the second reporting.
rep2 := c.client.pollBug()
if rep2.ID == "" || rep2.ID == rep.ID {
t.Fatalf("bad report ID: %q", rep2.ID)
}
want.ID = rep2.ID
want.First = true
want.Config = []byte(`{"Index":2}`)
want.NumCrashes = 3
c.expectEQ(rep2, want)
// Check that that we can't upstream the bug in the final reporting.
reply, _ = c.client.ReportingUpdate(&dashapi.BugUpdate{
ID: rep2.ID,
Status: dashapi.BugStatusUpstream,
})
c.expectEQ(reply.OK, false)
}
func TestInvalidBug(t *testing.T) {
c := NewCtx(t)
defer c.Close()
build := testBuild(1)
c.client.UploadBuild(build)
crash1 := testCrashWithRepro(build, 1)
c.client.ReportCrash(crash1)
rep := c.client.pollBug()
c.expectEQ(rep.Title, "title1")
reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{
ID: rep.ID,
Status: dashapi.BugStatusOpen,
ReproLevel: dashapi.ReproLevelC,
})
c.expectEQ(reply.OK, true)
{
closed, _ := c.client.ReportingPollClosed([]string{rep.ID, "foobar"})
c.expectEQ(len(closed), 0)
}
// Mark the bug as invalid.
c.client.updateBug(rep.ID, dashapi.BugStatusInvalid, "")
{
closed, _ := c.client.ReportingPollClosed([]string{rep.ID, "foobar"})
c.expectEQ(len(closed), 1)
c.expectEQ(closed[0], rep.ID)
}
// Now it should not be reported in either reporting.
c.client.pollBugs(0)
// Now a similar crash happens again.
crash2 := &dashapi.Crash{
BuildID: "build1",
Title: "title1",
Log: []byte("log2"),
Report: []byte("report2"),
ReproC: []byte("int main() { return 1; }"),
}
c.client.ReportCrash(crash2)
// Now it should be reported again.
rep = c.client.pollBug()
if rep.ID == "" {
t.Fatalf("empty report ID")
}
_, dbCrash, dbBuild := c.loadBug(rep.ID)
want := &dashapi.BugReport{
Namespace: "test1",
Config: []byte(`{"Index":1}`),
ID: rep.ID,
First: true,
Title: "title1 (2)",
CompilerID: "compiler1",
KernelRepo: "repo1",
KernelRepoAlias: "repo1/branch1",
KernelBranch: "branch1",
KernelCommit: "1111111111111111111111111111111111111111",
KernelCommitTitle: build.KernelCommitTitle,
KernelCommitDate: buildCommitDate,
KernelConfig: []byte("config1"),
KernelConfigLink: externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig),
Log: []byte("log2"),
LogLink: externalLink(c.ctx, textCrashLog, dbCrash.Log),
Report: []byte("report2"),
ReportLink: externalLink(c.ctx, textCrashReport, dbCrash.Report),
ReproC: []byte("int main() { return 1; }"),
ReproCLink: externalLink(c.ctx, textReproC, dbCrash.ReproC),
CrashID: rep.CrashID,
NumCrashes: 1,
HappenedOn: []string{"repo1/branch1"},
}
c.expectEQ(rep, want)
c.client.ReportFailedRepro(testCrashID(crash1))
}
func TestReportingQuota(t *testing.T) {
c := NewCtx(t)
defer c.Close()
build := testBuild(1)
c.client.UploadBuild(build)
const numReports = 8 // quota is 3 per day
for i := 0; i < numReports; i++ {
c.client.ReportCrash(testCrash(build, i))
}
for _, reports := range []int{3, 3, 2, 0, 0} {
c.advanceTime(24 * time.Hour)
c.client.pollBugs(reports)
// Out of quota for today, so must get 0 reports.
c.client.pollBugs(0)
}
}
// Basic dup scenario: mark one bug as dup of another.
func TestReportingDup(t *testing.T) {
c := NewCtx(t)
defer c.Close()
build := testBuild(1)
c.client.UploadBuild(build)
crash1 := testCrash(build, 1)
c.client.ReportCrash(crash1)
crash2 := testCrash(build, 2)
c.client.ReportCrash(crash2)
reports := c.client.pollBugs(2)
rep1 := reports[0]
rep2 := reports[1]
// Dup.
c.client.updateBug(rep2.ID, dashapi.BugStatusDup, rep1.ID)
{
// Both must be reported as open.
closed, _ := c.client.ReportingPollClosed([]string{rep1.ID, rep2.ID})
c.expectEQ(len(closed), 0)
}
// Undup.
c.client.updateBug(rep2.ID, dashapi.BugStatusOpen, "")
// Dup again.
c.client.updateBug(rep2.ID, dashapi.BugStatusDup, rep1.ID)
// Dup crash happens again, new bug must not be created.
c.client.ReportCrash(crash2)
c.client.pollBugs(0)
// Now close the original bug, and check that new bugs for dup are now created.
c.client.updateBug(rep1.ID, dashapi.BugStatusInvalid, "")
{
// Now both must be reported as closed.
closed, _ := c.client.ReportingPollClosed([]string{rep1.ID, rep2.ID})
c.expectEQ(len(closed), 2)
c.expectEQ(closed[0], rep1.ID)
c.expectEQ(closed[1], rep2.ID)
}
c.client.ReportCrash(crash2)
rep3 := c.client.pollBug()
c.expectEQ(rep3.Title, crash2.Title+" (2)")
// Unduping after the canonical bugs was closed must not work
// (we already created new bug for this report).
reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{
ID: rep2.ID,
Status: dashapi.BugStatusOpen,
})
c.expectEQ(reply.OK, false)
}
// Dup bug onto a closed bug.
// A new crash report must create a new bug.
func TestReportingDupToClosed(t *testing.T) {
c := NewCtx(t)
defer c.Close()
build := testBuild(1)
c.client.UploadBuild(build)
crash1 := testCrash(build, 1)
c.client.ReportCrash(crash1)
crash2 := testCrash(build, 2)
c.client.ReportCrash(crash2)
reports := c.client.pollBugs(2)
c.client.updateBug(reports[0].ID, dashapi.BugStatusInvalid, "")
c.client.updateBug(reports[1].ID, dashapi.BugStatusDup, reports[0].ID)
c.client.ReportCrash(crash2)
rep2 := c.client.pollBug()
c.expectEQ(rep2.Title, crash2.Title+" (2)")
}
// Test that marking dups across reporting levels is not permitted.
func TestReportingDupCrossReporting(t *testing.T) {
c := NewCtx(t)
defer c.Close()
build := testBuild(1)
c.client.UploadBuild(build)
crash1 := testCrash(build, 1)
c.client.ReportCrash(crash1)
crash2 := testCrash(build, 2)
c.client.ReportCrash(crash2)
reports := c.client.pollBugs(2)
rep1 := reports[0]
rep2 := reports[1]
// Upstream second bug.
c.client.updateBug(rep2.ID, dashapi.BugStatusUpstream, "")
rep3 := c.client.pollBug()
{
closed, _ := c.client.ReportingPollClosed([]string{rep1.ID, rep2.ID, rep3.ID})
c.expectEQ(len(closed), 1)
c.expectEQ(closed[0], rep2.ID)
}
// Duping must fail all ways.
cmds := []*dashapi.BugUpdate{
{ID: rep1.ID, DupOf: rep1.ID},
{ID: rep1.ID, DupOf: rep2.ID},
{ID: rep1.ID, DupOf: rep3.ID},
{ID: rep2.ID, DupOf: rep1.ID},
{ID: rep2.ID, DupOf: rep2.ID},
{ID: rep2.ID, DupOf: rep3.ID},
{ID: rep3.ID, DupOf: rep1.ID},
{ID: rep3.ID, DupOf: rep2.ID},
{ID: rep3.ID, DupOf: rep3.ID},
}
for _, cmd := range cmds {
t.Logf("duping %v -> %v", cmd.ID, cmd.DupOf)
cmd.Status = dashapi.BugStatusDup
reply, _ := c.client.ReportingUpdate(cmd)
c.expectEQ(reply.OK, false)
}
}
func TestReportingFilter(t *testing.T) {
c := NewCtx(t)
defer c.Close()
build := testBuild(1)
c.client.UploadBuild(build)
crash1 := testCrash(build, 1)
crash1.Title = "skip without repro 1"
c.client.ReportCrash(crash1)
// This does not skip first reporting, because it does not have repro.
rep1 := c.client.pollBug()
c.expectEQ(string(rep1.Config), `{"Index":1}`)
crash1.ReproSyz = []byte("getpid()")
c.client.ReportCrash(crash1)
// This has repro but was already reported to first reporting,
// so repro must go to the first reporting as well.
rep2 := c.client.pollBug()
c.expectEQ(string(rep2.Config), `{"Index":1}`)
// Now upstream it and it must go to the second reporting.
c.client.updateBug(rep1.ID, dashapi.BugStatusUpstream, "")
rep3 := c.client.pollBug()
c.expectEQ(string(rep3.Config), `{"Index":2}`)
// Now report a bug that must go to the second reporting right away.
crash2 := testCrash(build, 2)
crash2.Title = "skip without repro 2"
crash2.ReproSyz = []byte("getpid()")
c.client.ReportCrash(crash2)
rep4 := c.client.pollBug()
c.expectEQ(string(rep4.Config), `{"Index":2}`)
}