// Copyright 2019 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. package main import ( "context" "fmt" "strings" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/google/syzkaller/dashboard/dashapi" "github.com/google/syzkaller/pkg/email" ) func TestEmailNotifUpstreamEmbargo(t *testing.T) { c := NewCtx(t) defer c.Close() build := testBuild(1) c.client2.UploadBuild(build) crash := testCrash(build, 1) c.client2.ReportCrash(crash) report := c.pollEmailBug() c.expectEQ(report.To, []string{"test@syzkaller.com"}) // Upstreaming happens after 14 days, so no emails yet. c.advanceTime(13 * 24 * time.Hour) c.expectNoEmail() // Now we should get notification about upstreaming and upstream report: c.advanceTime(2 * 24 * time.Hour) notifUpstream := c.pollEmailBug() upstreamReport := c.pollEmailBug() c.expectEQ(notifUpstream.Subject, crash.Title) c.expectEQ(notifUpstream.Sender, report.Sender) c.expectEQ(notifUpstream.Body, "Sending this report to the next reporting stage.") c.expectEQ(upstreamReport.Subject, "[syzbot] "+crash.Title) c.expectNE(upstreamReport.Sender, report.Sender) c.expectEQ(upstreamReport.To, []string{"bugs@syzkaller.com", "default@maintainers.com"}) } func TestEmailNotifUpstreamSkip(t *testing.T) { c := NewCtx(t) defer c.Close() build := testBuild(1) c.client2.UploadBuild(build) crash := testCrash(build, 1) crash.Title = "skip with repro 1" c.client2.ReportCrash(crash) report := c.pollEmailBug() c.expectEQ(report.To, []string{"test@syzkaller.com"}) // No emails yet. c.expectNoEmail() // Now upload repro and it should be auto-upstreamed. crash.ReproOpts = []byte("repro opts") crash.ReproSyz = []byte("getpid()") c.client2.ReportCrash(crash) notifUpstream := c.pollEmailBug() upstreamReport := c.pollEmailBug() c.expectEQ(notifUpstream.Sender, report.Sender) c.expectEQ(notifUpstream.Body, "Sending this report to the next reporting stage.") c.expectNE(upstreamReport.Sender, report.Sender) c.expectEQ(upstreamReport.To, []string{"bugs@syzkaller.com", "default@maintainers.com"}) } func TestEmailNotifBadFix(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.publicClient build := testBuild(1) client.UploadBuild(build) // Fake more active managers. for i := 1; i < 5; i++ { client.UploadBuild(testBuild(i + 1)) } crash := testCrash(build, 1) client.ReportCrash(crash) report := c.pollEmailBug() c.expectEQ(report.To, []string{"test@syzkaller.com"}) _, extBugID, err := email.RemoveAddrContext(report.Sender) c.expectOK(err) c.incomingEmail(report.Sender, "#syz fix some: commit title") c.expectNoEmail() // Notification about bad fixing commit should be send after 90 days. c.advanceTime(50 * 24 * time.Hour) c.expectNoEmail() c.advanceTime(35 * 24 * time.Hour) c.expectNoEmail() c.advanceTime(10 * 24 * time.Hour) notif := c.pollEmailBug() t.Logf("%s", notif.Body) expectReply := fmt.Sprintf(`This bug is marked as fixed by commit: some: commit title But I can't find it in the tested trees[1] for more than 90 days. Is it a correct commit? Please update it by replying: #syz fix: exact-commit-title Until then the bug is still considered open and new crashes with the same signature are ignored. Kernel: access-public-email Dashboard link: https://testapp.appspot.com/bug?extid=%s --- [1] I expect the commit to be present in: 1. branch1 branch of repo1 2. branch2 branch of repo2 3. branch3 branch of repo3 4. branch4 branch of repo4 The full list of 5 trees can be found at https://testapp.appspot.com/access-public-email/repos `, extBugID) if diff := cmp.Diff(expectReply, notif.Body); diff != "" { t.Errorf("wrong notification text: %s", diff) fmt.Printf("received notification:\n%s\n", notif.Body) } // No notifications for another 14 days, then another one. c.advanceTime(13 * 24 * time.Hour) c.expectNoEmail() c.advanceTime(2 * 24 * time.Hour) notif = c.pollEmailBug() if !strings.Contains(notif.Body, "This bug is marked as fixed by commit:\nsome: commit title\n") { t.Fatalf("bad notification text: %q", notif.Body) } } func TestBugObsoleting(t *testing.T) { // To simplify test we specify all dates in days from a fixed point in time. const day = 24 * time.Hour days := func(n int) time.Time { t := time.Date(2000, 0, 0, 0, 0, 0, 0, time.UTC) return t.Add(time.Duration(n+1) * day) } tests := []struct { bug *Bug period time.Duration }{ // Final bug with just 1 crash: max final period. { bug: &Bug{ FirstTime: days(0), LastTime: days(0), NumCrashes: 1, Reporting: []BugReporting{{Reported: days(0)}}, }, period: 100 * day, }, // Non-final bug with just 1 crash: max non-final period. { bug: &Bug{ FirstTime: days(0), LastTime: days(0), NumCrashes: 1, Reporting: []BugReporting{{Reported: days(0)}, {}}, }, period: 60 * day, }, // Special manger: max period that that manager. { bug: &Bug{ FirstTime: days(0), LastTime: days(0), NumCrashes: 1, HappenedOn: []string{"special-obsoleting"}, Reporting: []BugReporting{{Reported: days(0)}, {}}, }, period: 20 * day, }, // Special manger and a non-special: normal rules. { bug: &Bug{ FirstTime: days(0), LastTime: days(0), NumCrashes: 1, HappenedOn: []string{"special-obsoleting", "non-special-manager"}, Reporting: []BugReporting{{Reported: days(0)}}, }, period: 100 * day, }, // Happened a lot: min period. { bug: &Bug{ FirstTime: days(0), LastTime: days(1), NumCrashes: 1000, Reporting: []BugReporting{{Reported: days(0)}}, }, period: 80 * day, }, } c := context.Background() for i, test := range tests { test.bug.Namespace = "test1" got := test.bug.obsoletePeriod(c) if got != test.period { t.Errorf("test #%v: got: %.2f, want %.2f", i, float64(got/time.Hour)/24, float64(test.period/time.Hour)/24) } } } func TestEmailNotifObsoleted(t *testing.T) { c := NewCtx(t) defer c.Close() build := testBuild(1) c.client2.UploadBuild(build) crash := testCrash(build, 1) crash.Maintainers = []string{"maintainer@syzkaller.com"} c.client2.ReportCrash(crash) report := c.pollEmailBug() // Need to upstream so that it's not auto-upstreamed before obsoleted. c.incomingEmail(report.Sender, "#syz upstream") report = c.pollEmailBug() // Add more people to bug CC. c.incomingEmail(report.Sender, "wow", EmailOptCC([]string{"somebody@else.com"})) // Bug is open, new crashes don't create new bug. c.client2.ReportCrash(crash) c.expectNoEmail() // Not yet. c.advanceTime(59 * 24 * time.Hour) c.expectNoEmail() // Now! c.advanceTime(2 * 24 * time.Hour) notif := c.pollEmailBug() if !strings.Contains(notif.Body, "Auto-closing this bug as obsolete") { t.Fatalf("bad notification text: %q", notif.Body) } c.expectEQ(notif.To, []string{"bugs@syzkaller.com", "default@maintainers.com", "default@sender.com", "somebody@else.com"}) // New crash must create new bug. c.client2.ReportCrash(crash) report = c.pollEmailBug() c.expectEQ(report.Subject, "title1 (2)") // Now the same, but for the last reporting (must have smaller CC list). c.incomingEmail(report.Sender, "#syz upstream", EmailOptCC([]string{"test@syzkaller.com"})) report = c.pollEmailBug() c.incomingEmail(report.Sender, "#syz upstream", EmailOptCC([]string{"bugs@syzkaller.com", "default@maintainers.com"})) report = c.pollEmailBug() _ = report c.advanceTime(101 * 24 * time.Hour) notif = c.pollEmailBug() if !strings.Contains(notif.Body, "Auto-closing this bug as obsolete") { t.Fatalf("bad notification text: %q", notif.Body) } c.expectEQ(notif.Subject, crash.Title+" (2)") c.expectEQ(notif.To, []string{"bugs2@syzkaller.com"}) } func TestEmailNotifNotObsoleted(t *testing.T) { c := NewCtx(t) defer c.Close() build := testBuild(1) c.client2.UploadBuild(build) // Crashes with repro are not auto-obsoleted. crash1 := testCrash(build, 1) crash1.ReproSyz = []byte("repro") c.client2.ReportCrash(crash1) report1 := c.pollEmailBug() c.incomingEmail(report1.Sender, "#syz upstream") report1 = c.pollEmailBug() _ = report1 // This crash will get another crash later. crash2 := testCrash(build, 2) c.client2.ReportCrash(crash2) report2 := c.pollEmailBug() c.incomingEmail(report2.Sender, "#syz upstream") report2 = c.pollEmailBug() _ = report2 // This crash will get some activity later. crash3 := testCrash(build, 3) c.client2.ReportCrash(crash3) report3 := c.pollEmailBug() c.incomingEmail(report3.Sender, "#syz upstream") report3 = c.pollEmailBug() // This will be obsoleted (just to check that we have timings right). c.advanceTime(24 * time.Hour) crash4 := testCrash(build, 4) c.client2.ReportCrash(crash4) report4 := c.pollEmailBug() c.incomingEmail(report4.Sender, "#syz upstream") report4 = c.pollEmailBug() c.advanceTime(59 * 24 * time.Hour) c.expectNoEmail() c.client2.ReportCrash(crash2) c.incomingEmail(report3.Sender, "I am looking at it") c.advanceTime(5 * 24 * time.Hour) // Only crash 4 is obsoleted. notif := c.pollEmailBug() c.expectEQ(notif.Sender, report4.Sender) c.expectNoEmail() // Crash 3 also obsoleted after some time. c.advanceTime(20 * 24 * time.Hour) notif = c.pollEmailBug() c.expectEQ(notif.Sender, report3.Sender) } func TestEmailNotifObsoletedManager(t *testing.T) { // Crashes with repro are auto-obsoleted if happen on a particular manager only. c := NewCtx(t) defer c.Close() build := testBuild(1) build.Manager = noFixBisectionManager c.client2.UploadBuild(build) crash := testCrashWithRepro(build, 1) c.client2.ReportCrash(crash) report := c.pollEmailBug() c.incomingEmail(report.Sender, "#syz upstream") report = c.pollEmailBug() _ = report c.advanceTime(200 * 24 * time.Hour) notif := c.pollEmailBug() c.expectTrue(strings.Contains(notif.Body, "Auto-closing this bug as obsolete")) } func TestExtNotifUpstreamEmbargo(t *testing.T) { c := NewCtx(t) defer c.Close() build1 := testBuild(1) c.client.UploadBuild(build1) crash1 := testCrash(build1, 1) c.client.ReportCrash(crash1) rep := c.client.pollBug() // Specify fixing commit for the bug. reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ ID: rep.ID, Status: dashapi.BugStatusOpen, }) c.expectEQ(reply.OK, true) c.client.pollNotifs(0) c.advanceTime(20 * 24 * time.Hour) notif := c.client.pollNotifs(1)[0] c.expectEQ(notif.ID, rep.ID) c.expectEQ(notif.Type, dashapi.BugNotifUpstream) } func TestExtNotifUpstreamOnHold(t *testing.T) { c := NewCtx(t) defer c.Close() build1 := testBuild(1) c.client.UploadBuild(build1) crash1 := testCrash(build1, 1) c.client.ReportCrash(crash1) rep := c.client.pollBug() // Specify fixing commit for the bug. reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ ID: rep.ID, Status: dashapi.BugStatusOpen, OnHold: true, }) c.expectEQ(reply.OK, true) c.advanceTime(20 * 24 * time.Hour) c.client.pollNotifs(0) }