syzkaller/dashboard/app/reporting_test.go
Dmitry Vyukov 632b86c938 dashboard/app: better errors from bug update command
Provide better errors from bug update command.
In particular, distinguish between bad updates and internal errors.
Also better messages.
Allow duping onto closed bugs.
Don't allow unduping from closed bugs.
2017-10-23 09:59:39 +02:00

427 lines
12 KiB
Go

// 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 (
"fmt"
"testing"
"time"
"github.com/google/syzkaller/dashboard/dashapi"
)
func TestReportBug(t *testing.T) {
c := NewCtx(t)
defer c.Close()
build := testBuild(1)
c.expectOK(c.API(client1, key1, "upload_build", build, nil))
crash1 := &dashapi.Crash{
BuildID: "build1",
Title: "title1",
Maintainers: []string{`"Foo Bar" <foo@bar.com>`, `bar@foo.com`},
Log: []byte("log1"),
Report: []byte("report1"),
}
c.expectOK(c.API(client1, key1, "report_crash", crash1, nil))
// Must get no reports for "unknown" type.
pr := &dashapi.PollRequest{
Type: "unknown",
}
resp := new(dashapi.PollResponse)
c.expectOK(c.API(client1, key1, "reporting_poll", pr, resp))
c.expectEQ(len(resp.Reports), 0)
// Must get a proper report for "test" type.
pr.Type = "test"
c.expectOK(c.API(client1, key1, "reporting_poll", pr, resp))
c.expectEQ(len(resp.Reports), 1)
rep := resp.Reports[0]
if rep.ID == "" {
t.Fatalf("empty report ID")
}
want := &dashapi.BugReport{
Config: []byte(`{"Index":1}`),
ID: rep.ID,
First: true,
Title: "title1",
Maintainers: []string{"bar@foo.com", "foo@bar.com"},
CompilerID: "compiler1",
KernelRepo: "repo1",
KernelBranch: "branch1",
KernelCommit: "kernel_commit1",
KernelConfig: []byte("config1"),
Log: []byte("log1"),
Report: []byte("report1"),
}
c.expectEQ(rep, want)
// Since we did not update bug status yet, should get the same report again.
reports := reportAllBugs(c, 1)
c.expectEQ(reports[0], 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("#some opts\ngetpid()")
c.expectOK(c.API(client1, key1, "report_crash", crash1, nil))
reports = reportAllBugs(c, 1)
c.expectEQ(reports[0], want)
cmd := &dashapi.BugUpdate{
ID: rep.ID,
Status: dashapi.BugStatusOpen,
ReproLevel: dashapi.ReproLevelSyz,
}
reply := new(dashapi.BugUpdateReply)
c.expectOK(c.API(client1, key1, "reporting_update", cmd, reply))
c.expectEQ(reply.OK, true)
// After bug update should not get the report again.
c.expectOK(c.API(client1, key1, "reporting_poll", pr, resp))
c.expectEQ(len(resp.Reports), 0)
// Now close the bug in the first reporting.
cmd = &dashapi.BugUpdate{
ID: rep.ID,
Status: dashapi.BugStatusUpstream,
}
c.expectOK(c.API(client1, key1, "reporting_update", cmd, reply))
c.expectEQ(reply.OK, true)
// Check that bug updates for the first reporting fail now.
cmd = &dashapi.BugUpdate{
ID: rep.ID,
Status: dashapi.BugStatusOpen,
}
c.expectOK(c.API(client1, key1, "reporting_update", cmd, reply))
c.expectEQ(reply.OK, false)
// Check that we get the report in the second reporting.
c.expectOK(c.API(client1, key1, "reporting_poll", pr, resp))
c.expectEQ(len(resp.Reports), 1)
rep2 := resp.Reports[0]
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}`)
c.expectEQ(rep2, want)
// Check that that we can't upstream the bug in the final reporting.
cmd = &dashapi.BugUpdate{
ID: rep2.ID,
Status: dashapi.BugStatusUpstream,
}
c.expectOK(c.API(client1, key1, "reporting_update", cmd, reply))
c.expectEQ(reply.OK, false)
}
func TestInvalidBug(t *testing.T) {
c := NewCtx(t)
defer c.Close()
build := testBuild(1)
c.expectOK(c.API(client1, key1, "upload_build", build, nil))
crash1 := testCrash(build, 1)
crash1.ReproC = []byte("int main() {}")
c.expectOK(c.API(client1, key1, "report_crash", crash1, nil))
pr := &dashapi.PollRequest{
Type: "test",
}
resp := new(dashapi.PollResponse)
c.expectOK(c.API(client1, key1, "reporting_poll", pr, resp))
c.expectEQ(len(resp.Reports), 1)
rep := resp.Reports[0]
c.expectEQ(rep.Title, "title1")
cmd := &dashapi.BugUpdate{
ID: rep.ID,
Status: dashapi.BugStatusOpen,
ReproLevel: dashapi.ReproLevelC,
}
reply := new(dashapi.BugUpdateReply)
c.expectOK(c.API(client1, key1, "reporting_update", cmd, reply))
c.expectEQ(reply.OK, true)
// Mark the bug as invalid.
cmd = &dashapi.BugUpdate{
ID: rep.ID,
Status: dashapi.BugStatusInvalid,
}
c.expectOK(c.API(client1, key1, "reporting_update", cmd, reply))
c.expectEQ(reply.OK, true)
// Now it should not be reported in either reporting.
c.expectOK(c.API(client1, key1, "reporting_poll", pr, resp))
c.expectEQ(len(resp.Reports), 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.expectOK(c.API(client1, key1, "report_crash", crash2, nil))
// Now it should be reported again.
c.expectOK(c.API(client1, key1, "reporting_poll", pr, resp))
c.expectEQ(len(resp.Reports), 1)
rep = resp.Reports[0]
if rep.ID == "" {
t.Fatalf("empty report ID")
}
want := &dashapi.BugReport{
Config: []byte(`{"Index":1}`),
ID: rep.ID,
First: true,
Title: "title1 (2)",
CompilerID: "compiler1",
KernelRepo: "repo1",
KernelBranch: "branch1",
KernelCommit: "kernel_commit1",
KernelConfig: []byte("config1"),
Log: []byte("log2"),
Report: []byte("report2"),
ReproC: []byte("int main() { return 1; }"),
}
c.expectEQ(rep, want)
cid := &dashapi.CrashID{
BuildID: build.ID,
Title: crash1.Title,
}
c.expectOK(c.API(client1, key1, "report_failed_repro", cid, nil))
}
func TestReportingQuota(t *testing.T) {
c := NewCtx(t)
defer c.Close()
build := testBuild(1)
c.expectOK(c.API(client1, key1, "upload_build", build, nil))
const numReports = 8 // quota is 3 per day
for i := 0; i < numReports; i++ {
crash := &dashapi.Crash{
BuildID: "build1",
Title: fmt.Sprintf("title%v", i),
Log: []byte(fmt.Sprintf("log%v", i)),
Report: []byte(fmt.Sprintf("report%v", i)),
}
c.expectOK(c.API(client1, key1, "report_crash", crash, nil))
}
for _, reports := range []int{3, 3, 2, 0, 0} {
c.advanceTime(24 * time.Hour)
pr := &dashapi.PollRequest{
Type: "test",
}
resp := new(dashapi.PollResponse)
c.expectOK(c.API(client1, key1, "reporting_poll", pr, resp))
c.expectEQ(len(resp.Reports), reports)
for _, rep := range resp.Reports {
cmd := &dashapi.BugUpdate{
ID: rep.ID,
Status: dashapi.BugStatusOpen,
}
reply := new(dashapi.BugUpdateReply)
c.expectOK(c.API(client1, key1, "reporting_update", cmd, reply))
c.expectEQ(reply.OK, true)
}
// Out of quota for today, so must get 0 reports.
c.expectOK(c.API(client1, key1, "reporting_poll", pr, resp))
c.expectEQ(len(resp.Reports), 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.expectOK(c.API(client1, key1, "upload_build", build, nil))
crash1 := testCrash(build, 1)
c.expectOK(c.API(client1, key1, "report_crash", crash1, nil))
crash2 := testCrash(build, 2)
c.expectOK(c.API(client1, key1, "report_crash", crash2, nil))
pr := &dashapi.PollRequest{
Type: "test",
}
resp := new(dashapi.PollResponse)
c.expectOK(c.API(client1, key1, "reporting_poll", pr, resp))
c.expectEQ(len(resp.Reports), 2)
rep1 := resp.Reports[0]
cmd := &dashapi.BugUpdate{
ID: rep1.ID,
Status: dashapi.BugStatusOpen,
}
reply := new(dashapi.BugUpdateReply)
c.expectOK(c.API(client1, key1, "reporting_update", cmd, reply))
c.expectEQ(reply.OK, true)
rep2 := resp.Reports[1]
cmd = &dashapi.BugUpdate{
ID: rep2.ID,
Status: dashapi.BugStatusOpen,
}
c.expectOK(c.API(client1, key1, "reporting_update", cmd, reply))
c.expectEQ(reply.OK, true)
// Dup.
cmd = &dashapi.BugUpdate{
ID: rep2.ID,
Status: dashapi.BugStatusDup,
DupOf: rep1.ID,
}
c.expectOK(c.API(client1, key1, "reporting_update", cmd, reply))
c.expectEQ(reply.OK, true)
// Undup.
cmd = &dashapi.BugUpdate{
ID: rep2.ID,
Status: dashapi.BugStatusOpen,
}
c.expectOK(c.API(client1, key1, "reporting_update", cmd, reply))
c.expectEQ(reply.OK, true)
// Dup again.
cmd = &dashapi.BugUpdate{
ID: rep2.ID,
Status: dashapi.BugStatusDup,
DupOf: rep1.ID,
}
c.expectOK(c.API(client1, key1, "reporting_update", cmd, reply))
c.expectEQ(reply.OK, true)
// Dup crash happens again, new bug must not be created.
c.expectOK(c.API(client1, key1, "report_crash", crash2, nil))
c.expectOK(c.API(client1, key1, "reporting_poll", pr, resp))
c.expectEQ(len(resp.Reports), 0)
// Now close the original bug, and check that new bugs for dup are now created.
cmd = &dashapi.BugUpdate{
ID: rep1.ID,
Status: dashapi.BugStatusInvalid,
}
c.expectOK(c.API(client1, key1, "reporting_update", cmd, reply))
c.expectEQ(reply.OK, true)
c.expectOK(c.API(client1, key1, "report_crash", crash2, nil))
c.expectOK(c.API(client1, key1, "reporting_poll", pr, resp))
c.expectEQ(len(resp.Reports), 1)
c.expectEQ(resp.Reports[0].Title, crash2.Title+" (2)")
// Unduping after the canonical bugs was closed must not work
// (we already created new bug for this report).
cmd = &dashapi.BugUpdate{
ID: rep2.ID,
Status: dashapi.BugStatusOpen,
}
c.expectOK(c.API(client1, key1, "reporting_update", cmd, reply))
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.expectOK(c.API(client1, key1, "upload_build", build, nil))
crash1 := testCrash(build, 1)
c.expectOK(c.API(client1, key1, "report_crash", crash1, nil))
crash2 := testCrash(build, 2)
c.expectOK(c.API(client1, key1, "report_crash", crash2, nil))
reports := reportAllBugs(c, 2)
cmd := &dashapi.BugUpdate{
ID: reports[0].ID,
Status: dashapi.BugStatusInvalid,
}
reply := new(dashapi.BugUpdateReply)
c.expectOK(c.API(client1, key1, "reporting_update", cmd, reply))
c.expectEQ(reply.OK, true)
cmd = &dashapi.BugUpdate{
ID: reports[1].ID,
Status: dashapi.BugStatusDup,
DupOf: reports[0].ID,
}
c.expectOK(c.API(client1, key1, "reporting_update", cmd, reply))
c.expectEQ(reply.OK, true)
c.expectOK(c.API(client1, key1, "report_crash", crash2, nil))
reports2 := reportAllBugs(c, 1)
c.expectEQ(reports2[0].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.expectOK(c.API(client1, key1, "upload_build", build, nil))
crash1 := testCrash(build, 1)
c.expectOK(c.API(client1, key1, "report_crash", crash1, nil))
crash2 := testCrash(build, 2)
c.expectOK(c.API(client1, key1, "report_crash", crash2, nil))
reports := reportAllBugs(c, 2)
rep1 := reports[0]
rep2 := reports[1]
// Upstream second bug.
cmd := &dashapi.BugUpdate{
ID: rep2.ID,
Status: dashapi.BugStatusUpstream,
}
reply := new(dashapi.BugUpdateReply)
c.expectOK(c.API(client1, key1, "reporting_update", cmd, reply))
c.expectEQ(reply.OK, true)
reports = reportAllBugs(c, 1)
rep3 := reports[0]
// Duping must fail all ways.
cmds := []*dashapi.BugUpdate{
&dashapi.BugUpdate{ID: rep1.ID, DupOf: rep1.ID},
&dashapi.BugUpdate{ID: rep1.ID, DupOf: rep2.ID},
&dashapi.BugUpdate{ID: rep1.ID, DupOf: rep3.ID},
&dashapi.BugUpdate{ID: rep2.ID, DupOf: rep1.ID},
&dashapi.BugUpdate{ID: rep2.ID, DupOf: rep2.ID},
&dashapi.BugUpdate{ID: rep2.ID, DupOf: rep3.ID},
&dashapi.BugUpdate{ID: rep3.ID, DupOf: rep1.ID},
&dashapi.BugUpdate{ID: rep3.ID, DupOf: rep2.ID},
&dashapi.BugUpdate{ID: rep3.ID, DupOf: rep3.ID},
}
for _, cmd := range cmds {
t.Logf("duping %v -> %v", cmd.ID, cmd.DupOf)
cmd.Status = dashapi.BugStatusDup
c.expectOK(c.API(client1, key1, "reporting_update", cmd, reply))
c.expectEQ(reply.OK, false)
}
}