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