// 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. package main import ( "bytes" "fmt" "strings" "testing" "time" "github.com/google/syzkaller/dashboard/dashapi" "github.com/google/syzkaller/pkg/email" "github.com/stretchr/testify/assert" db "google.golang.org/appengine/v2/datastore" ) const sampleGitPatch = `--- a/mm/kasan/kasan.c +++ b/mm/kasan/kasan.c - current->kasan_depth++; + current->kasan_depth--; ` const syzTestGitBranchSamplePatch = "#syz test: git://git.git/git.git kernel-branch\n" + sampleGitPatch // nolint: funlen func TestJob(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.publicClient build := testBuild(1) client.UploadBuild(build) // Report crash without repro, check that test requests are not accepted. crash := testCrash(build, 1) crash.Maintainers = []string{"maintainer@kernel.org"} client.ReportCrash(crash) sender := c.pollEmailBug().Sender c.incomingEmail(sender, "#syz upstream\n") sender = c.pollEmailBug().Sender _, extBugID, err := email.RemoveAddrContext(sender) c.expectOK(err) mailingList := c.config().Namespaces["access-public-email"].Reporting[0].Config.(*EmailConfig).Email c.incomingEmail(sender, "bla-bla-bla", EmailOptFrom("maintainer@kernel.org"), EmailOptCC([]string{mailingList, "kernel@mailing.list"})) c.incomingEmail(sender, syzTestGitBranchSamplePatch, EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList})) body := c.pollEmailBug().Body t.Logf("body: %s", body) c.expectEQ(strings.Contains(body, "This crash does not have a reproducer"), true) // Report crash with repro. crash.ReproOpts = []byte("repro opts") crash.ReproSyz = []byte("repro syz") crash.ReproC = []byte("repro C") client.ReportCrash(crash) client.pollAndFailBisectJob(build.Manager) body = c.pollEmailBug().Body c.expectEQ(strings.Contains(body, "syzbot has found a reproducer"), true) c.incomingEmail(sender, "#syz test: repo", EmailOptFrom("test@requester.com"), EmailOptSubject("my-subject"), EmailOptCC([]string{mailingList})) msg := c.pollEmailBug() c.expectEQ(strings.Contains(msg.Body, replyMalformedSyzTest), true) c.expectEQ(msg.Subject, "Re: my-subject") c.incomingEmail(sender, "#syz test: repo branch commit", EmailOptFrom("test@requester.com"), EmailOptSubject("Re: my-subject"), EmailOptCC([]string{mailingList})) msg = c.pollEmailBug() c.expectEQ(strings.Contains(msg.Body, replyMalformedSyzTest), true) c.expectEQ(msg.Subject, "Re: my-subject") c.incomingEmail(sender, "#syz test: repo branch", EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList})) body = c.pollEmailBug().Body c.expectEQ(strings.Contains(body, "does not look like a valid git repo"), true) c.incomingEmail(sender, syzTestGitBranchSamplePatch, EmailOptFrom("\"foo\" ")) c.expectNoEmail() pollResp := client.pollJobs(build.Manager) c.expectEQ(pollResp.ID, "") // This submits actual test request. c.incomingEmail(sender, syzTestGitBranchSamplePatch, EmailOptMessageID(1), EmailOptFrom("test@requester.com"), EmailOptCC([]string{"somebody@else.com", "test@syzkaller.com"})) c.expectNoEmail() // A dup of the same request with the same Message-ID. c.incomingEmail(sender, syzTestGitBranchSamplePatch, EmailOptMessageID(1), EmailOptFrom("test@requester.com"), EmailOptCC([]string{"somebody@else.com", "test@syzkaller.com"})) c.expectNoEmail() pollResp = client.pollJobs("foobar") c.expectEQ(pollResp.ID, "") pollResp = client.pollJobs(build.Manager) c.expectNE(pollResp.ID, "") c.expectEQ(pollResp.Type, dashapi.JobTestPatch) c.expectEQ(pollResp.Manager, build.Manager) c.expectEQ(pollResp.KernelRepo, "git://git.git/git.git") c.expectEQ(pollResp.KernelBranch, "kernel-branch") c.expectEQ(pollResp.KernelConfig, build.KernelConfig) c.expectEQ(pollResp.SyzkallerCommit, build.SyzkallerCommit) c.expectEQ(pollResp.Patch, []byte(sampleGitPatch)) c.expectEQ(pollResp.ReproOpts, []byte("repro opts")) c.expectEQ(pollResp.ReproSyz, []byte( "# See https://goo.gl/kgGztJ for information about syzkaller reproducers.\n"+ "#repro opts\n"+ "repro syz")) c.expectEQ(pollResp.ReproC, []byte("repro C")) jobDoneReq := &dashapi.JobDoneReq{ ID: pollResp.ID, Build: *build, CrashTitle: "test crash title", CrashLog: []byte("test crash log"), CrashReport: []byte("test crash report"), } client.JobDone(jobDoneReq) { dbJob, dbBuild, _ := c.loadJob(pollResp.ID) patchLink := externalLink(c.ctx, textPatch, dbJob.Patch) kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig) logLink := externalLink(c.ctx, textCrashLog, dbJob.CrashLog) msg := c.pollEmailBug() to := email.MergeEmailLists([]string{"test@requester.com", "somebody@else.com", mailingList}) c.expectEQ(msg.To, to) c.expectEQ(msg.Subject, "Re: [syzbot] "+crash.Title) c.expectEQ(len(msg.Attachments), 0) c.expectEQ(msg.Body, fmt.Sprintf(`Hello, syzbot has tested the proposed patch but the reproducer is still triggering an issue: test crash title test crash report Tested on: commit: 11111111 kernel_commit_title1 git tree: repo1 branch1 console output: %[3]v kernel config: %[2]v dashboard link: https://testapp.appspot.com/bug?extid=%[4]v compiler: compiler1 patch: %[1]v `, patchLink, kernelConfigLink, logLink, extBugID)) c.checkURLContents(patchLink, []byte(sampleGitPatch)) c.checkURLContents(kernelConfigLink, build.KernelConfig) c.checkURLContents(logLink, jobDoneReq.CrashLog) } // Testing fails with an error. c.incomingEmail(sender, syzTestGitBranchSamplePatch, EmailOptMessageID(2)) pollResp = client.pollJobs(build.Manager) c.expectEQ(pollResp.Type, dashapi.JobTestPatch) jobDoneReq = &dashapi.JobDoneReq{ ID: pollResp.ID, Build: *build, Error: []byte("failed to apply patch"), } client.JobDone(jobDoneReq) { dbJob, dbBuild, _ := c.loadJob(pollResp.ID) patchLink := externalLink(c.ctx, textPatch, dbJob.Patch) kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig) msg := c.pollEmailBug() c.expectEQ(len(msg.Attachments), 0) c.expectEQ(msg.Body, fmt.Sprintf(`Hello, syzbot tried to test the proposed patch but the build/boot failed: failed to apply patch Tested on: commit: 11111111 kernel_commit_title1 git tree: repo1 branch1 kernel config: %[2]v dashboard link: https://testapp.appspot.com/bug?extid=%[3]v compiler: compiler1 patch: %[1]v `, patchLink, kernelConfigLink, extBugID)) c.checkURLContents(patchLink, []byte(sampleGitPatch)) c.checkURLContents(kernelConfigLink, build.KernelConfig) } // Testing fails with a huge error that can't be inlined in email. c.incomingEmail(sender, syzTestGitBranchSamplePatch, EmailOptMessageID(3)) pollResp = client.pollJobs(build.Manager) c.expectEQ(pollResp.Type, dashapi.JobTestPatch) jobDoneReq = &dashapi.JobDoneReq{ ID: pollResp.ID, Build: *build, Error: bytes.Repeat([]byte{'a', 'b', 'c'}, (maxInlineError+100)/3), } client.JobDone(jobDoneReq) { dbJob, dbBuild, _ := c.loadJob(pollResp.ID) patchLink := externalLink(c.ctx, textPatch, dbJob.Patch) errorLink := externalLink(c.ctx, textError, dbJob.Error) kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig) msg := c.pollEmailBug() c.expectEQ(len(msg.Attachments), 0) truncatedError := string(jobDoneReq.Error[len(jobDoneReq.Error)-maxInlineError:]) c.expectEQ(msg.Body, fmt.Sprintf(`Hello, syzbot tried to test the proposed patch but the build/boot failed: %[1]v Error text is too large and was truncated, full error text is at: %[2]v Tested on: commit: 11111111 kernel_commit_title1 git tree: repo1 branch1 kernel config: %[4]v dashboard link: https://testapp.appspot.com/bug?extid=%[5]v compiler: compiler1 patch: %[3]v `, truncatedError, errorLink, patchLink, kernelConfigLink, extBugID)) c.checkURLContents(patchLink, []byte(sampleGitPatch)) c.checkURLContents(errorLink, jobDoneReq.Error) c.checkURLContents(kernelConfigLink, build.KernelConfig) } c.incomingEmail(sender, syzTestGitBranchSamplePatch, EmailOptMessageID(4)) pollResp = client.pollJobs(build.Manager) c.expectEQ(pollResp.Type, dashapi.JobTestPatch) jobDoneReq = &dashapi.JobDoneReq{ ID: pollResp.ID, Build: *build, CrashLog: []byte("console output"), } client.JobDone(jobDoneReq) { dbJob, dbBuild, _ := c.loadJob(pollResp.ID) patchLink := externalLink(c.ctx, textPatch, dbJob.Patch) logLink := externalLink(c.ctx, textCrashLog, dbJob.CrashLog) kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig) msg := c.pollEmailBug() c.expectEQ(len(msg.Attachments), 0) c.expectEQ(msg.Body, fmt.Sprintf(`Hello, syzbot has tested the proposed patch and the reproducer did not trigger any issue: Reported-by: syzbot+%[1]v@testapp.appspotmail.com Tested-by: syzbot+%[1]v@testapp.appspotmail.com Tested on: commit: 11111111 kernel_commit_title1 git tree: repo1 branch1 console output: %[4]v kernel config: %[3]v dashboard link: https://testapp.appspot.com/bug?extid=%[1]v compiler: compiler1 patch: %[2]v Note: testing is done by a robot and is best-effort only. `, extBugID, patchLink, kernelConfigLink, logLink)) c.checkURLContents(patchLink, []byte(sampleGitPatch)) c.checkURLContents(kernelConfigLink, build.KernelConfig) } pollResp = client.pollJobs(build.Manager) c.expectEQ(pollResp.ID, "") } // Test whether we can test boot time crashes. func TestBootErrorPatch(t *testing.T) { c := NewCtx(t) defer c.Close() build := testBuild(1) c.client2.UploadBuild(build) crash := testCrash(build, 2) crash.Title = "riscv/fixes boot error: can't ssh into the instance" c.client2.ReportCrash(crash) report := c.pollEmailBug() c.incomingEmail(report.Sender, "#syz upstream\n", EmailOptCC(report.To)) report = c.pollEmailBug() c.incomingEmail(report.Sender, syzTestGitBranchSamplePatch, EmailOptFrom("test@requester.com"), EmailOptCC(report.To)) c.expectNoEmail() pollResp := c.client2.pollJobs(build.Manager) c.expectEQ(pollResp.Type, dashapi.JobTestPatch) } const testErrorTitle = `upstream test error: WARNING in __queue_work` func TestTestErrorPatch(t *testing.T) { c := NewCtx(t) defer c.Close() build := testBuild(1) c.client2.UploadBuild(build) crash := testCrash(build, 2) crash.Title = testErrorTitle c.client2.ReportCrash(crash) sender := c.pollEmailBug().Sender c.incomingEmail(sender, "#syz upstream\n") report := c.pollEmailBug() c.incomingEmail(report.Sender, syzTestGitBranchSamplePatch, EmailOptFrom("test@requester.com"), EmailOptCC(report.To)) c.expectNoEmail() pollResp := c.client2.pollJobs(build.Manager) c.expectEQ(pollResp.Type, dashapi.JobTestPatch) } // Test on particular commit and without a patch. func TestJobWithoutPatch(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.publicClient build := testBuild(1) client.UploadBuild(build) crash := testCrash(build, 1) crash.ReproOpts = []byte("repro opts") crash.ReproSyz = []byte("repro syz") client.ReportCrash(crash) client.pollAndFailBisectJob(build.Manager) sender := c.pollEmailBug().Sender _, extBugID, err := email.RemoveAddrContext(sender) c.expectOK(err) // Patch testing should happen for bugs with fix commits too. c.incomingEmail(sender, "#syz fix: some commit title\n") c.incomingEmail(sender, "#syz test git://mygit.com/git.git 5e6a2eea\n", EmailOptMessageID(1)) c.expectNoEmail() pollResp := client.pollJobs(build.Manager) c.expectNE(pollResp.ID, "") c.expectEQ(pollResp.Type, dashapi.JobTestPatch) testBuild := testBuild(2) testBuild.KernelRepo = "git://mygit.com/git.git" testBuild.KernelBranch = "" testBuild.KernelCommit = "5e6a2eea5e6a2eea5e6a2eea5e6a2eea5e6a2eea" jobDoneReq := &dashapi.JobDoneReq{ ID: pollResp.ID, Build: *testBuild, } client.JobDone(jobDoneReq) { _, dbBuild, _ := c.loadJob(pollResp.ID) kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig) msg := c.pollEmailBug() c.expectEQ(len(msg.Attachments), 0) c.expectEQ(msg.Body, fmt.Sprintf(`Hello, syzbot has tested the proposed patch and the reproducer did not trigger any issue: Reported-by: syzbot+%[1]v@testapp.appspotmail.com Tested-by: syzbot+%[1]v@testapp.appspotmail.com Tested on: commit: 5e6a2eea kernel_commit_title2 git tree: git://mygit.com/git.git kernel config: %[2]v dashboard link: https://testapp.appspot.com/bug?extid=%[1]v compiler: compiler2 Note: no patches were applied. Note: testing is done by a robot and is best-effort only. `, extBugID, kernelConfigLink)) c.checkURLContents(kernelConfigLink, testBuild.KernelConfig) } pollResp = client.pollJobs(build.Manager) c.expectEQ(pollResp.ID, "") } func TestReproRetestJob(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.publicClient oldBuild := testBuild(1) oldBuild.KernelRepo = "git://mygit.com/git.git" oldBuild.KernelBranch = "main" client.UploadBuild(oldBuild) crash := testCrash(oldBuild, 1) crash.ReproOpts = []byte("repro opts") crash.ReproSyz = []byte("repro syz") client.ReportCrash(crash) sender := c.pollEmailBug().Sender _, extBugID, err := email.RemoveAddrContext(sender) c.expectOK(err) crash2 := testCrash(oldBuild, 1) crash2.ReproOpts = []byte("repro opts") crash2.ReproSyz = []byte("repro syz") crash2.ReproC = []byte("repro C") client.ReportCrash(crash2) c.pollEmailBug() // Upload a newer build. c.advanceTime(time.Minute) build := testBuild(1) build.ID = "new-build" build.KernelRepo = "git://mygit.com/new-git.git" build.KernelBranch = "new-main" build.KernelConfig = []byte{0xAB, 0xCD, 0xEF} client.UploadBuild(build) c.advanceTime(time.Hour) bug, _, _ := c.loadBug(extBugID) c.expectEQ(bug.ReproLevel, ReproLevelC) // Let's say that the C repro testing has failed. c.advanceTime(c.config().Obsoleting.ReproRetestStart + time.Hour) for i := 0; i < 2; i++ { resp := client.pollSpecificJobs(build.Manager, dashapi.ManagerJobs{TestPatches: true}) c.expectEQ(resp.Type, dashapi.JobTestPatch) c.expectEQ(resp.KernelRepo, build.KernelRepo) c.expectEQ(resp.KernelBranch, build.KernelBranch) c.expectEQ(resp.KernelConfig, build.KernelConfig) c.expectEQ(resp.Patch, []uint8(nil)) var done *dashapi.JobDoneReq if resp.ReproC == nil { // Pretend that the syz repro still works. done = &dashapi.JobDoneReq{ ID: resp.ID, CrashTitle: crash.Title, CrashLog: []byte("test crash log"), CrashReport: []byte("test crash report"), } } else { // Pretend that the C repro fails. done = &dashapi.JobDoneReq{ ID: resp.ID, } } client.expectOK(client.JobDone(done)) } // Expect that the repro level is no longer ReproLevelC. c.expectNoEmail() bug, _, _ = c.loadBug(extBugID) c.expectEQ(bug.HeadReproLevel, ReproLevelSyz) // Let's also deprecate the syz repro. c.advanceTime(c.config().Obsoleting.ReproRetestPeriod + time.Hour) resp := client.pollSpecificJobs(build.Manager, dashapi.ManagerJobs{TestPatches: true}) c.expectEQ(resp.Type, dashapi.JobTestPatch) c.expectEQ(resp.KernelBranch, build.KernelBranch) c.expectEQ(resp.ReproC, []uint8(nil)) c.expectEQ(resp.KernelConfig, build.KernelConfig) done := &dashapi.JobDoneReq{ ID: resp.ID, } client.expectOK(client.JobDone(done)) // Expect that the repro level is no longer ReproLevelC. bug, _, _ = c.loadBug(extBugID) c.expectEQ(bug.HeadReproLevel, ReproLevelNone) c.expectEQ(bug.ReproLevel, ReproLevelC) // Expect that the bug gets deprecated. notif := c.pollEmailBug() if !strings.Contains(notif.Body, "Auto-closing this bug as obsolete") { t.Fatalf("bad notification text: %q", notif.Body) } // Expect that the right obsoletion reason was set. bug, _, _ = c.loadBug(extBugID) c.expectEQ(bug.StatusReason, dashapi.InvalidatedByRevokedRepro) } func TestDelegatedManagerReproRetest(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.makeClient(clientMgrDecommission, keyMgrDecommission, true) oldManager := notYetDecommManger newManager := delegateToManager oldBuild := testBuild(1) oldBuild.KernelRepo = "git://delegated.repo/git.git" oldBuild.KernelBranch = "main" oldBuild.Manager = oldManager client.UploadBuild(oldBuild) crash := testCrash(oldBuild, 1) crash.ReproOpts = []byte("repro opts") crash.ReproSyz = []byte("repro syz") crash.ReproC = []byte("repro C") client.ReportCrash(crash) sender := c.pollEmailBug().Sender _, extBugID, err := email.RemoveAddrContext(sender) c.expectOK(err) // Deprecate the oldManager. c.decommissionManager("test-mgr-decommission", oldManager, newManager) // Upload a build for the new manager. c.advanceTime(time.Minute) build := testBuild(1) build.ID = "new-build" build.KernelRepo = "git://delegated.repo/new-git.git" build.KernelBranch = "new-main" build.KernelConfig = []byte{0xAB, 0xCD, 0xEF} build.Manager = newManager client.UploadBuild(build) // Wait until the bug is upstreamed. c.advanceTime(20 * 24 * time.Hour) c.pollEmailBug() c.pollEmailBug() // Let's say that the C repro testing has failed. c.advanceTime(c.config().Obsoleting.ReproRetestPeriod + time.Hour) resp := client.pollSpecificJobs(build.Manager, dashapi.ManagerJobs{TestPatches: true}) c.expectEQ(resp.Type, dashapi.JobTestPatch) c.expectEQ(resp.KernelRepo, build.KernelRepo) c.expectEQ(resp.KernelBranch, build.KernelBranch) c.expectEQ(resp.KernelConfig, build.KernelConfig) c.expectEQ(resp.Patch, []uint8(nil)) // Pretend that the C repro fails. done := &dashapi.JobDoneReq{ ID: resp.ID, } client.expectOK(client.JobDone(done)) // If it has worked, the repro is revoked and the bug is obsoleted. c.pollEmailBug() bug, _, _ := c.loadBug(extBugID) c.expectEQ(bug.HeadReproLevel, ReproLevelNone) } // Test on a restricted manager. func TestJobRestrictedManager(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.publicClient build := testBuild(1) build.Manager = restrictedManager client.UploadBuild(build) crash := testCrash(build, 1) crash.ReproSyz = []byte("repro syz") client.ReportCrash(crash) client.pollAndFailBisectJob(build.Manager) sender := c.pollEmailBug().Sender // Testing on a wrong repo must fail and no test jobs passed to manager. c.incomingEmail(sender, "#syz test: git://mygit.com/git.git master\n", EmailOptMessageID(1)) reply := c.pollEmailBug() c.expectEQ(strings.Contains(reply.Body, "you should test only on restricted.git"), true) pollResp := client.pollJobs(build.Manager) c.expectEQ(pollResp.ID, "") // Testing on the right repo must succeed. c.incomingEmail(sender, "#syz test: git://restricted.git/restricted.git master\n", EmailOptMessageID(2)) pollResp = client.pollJobs(build.Manager) c.expectNE(pollResp.ID, "") c.expectEQ(pollResp.Type, dashapi.JobTestPatch) c.expectEQ(pollResp.Manager, build.Manager) c.expectEQ(pollResp.KernelRepo, "git://restricted.git/restricted.git") } // Test that JobBisectFix is returned only after 30 days. func TestBisectFixJob(t *testing.T) { c := NewCtx(t) defer c.Close() // Upload a crash report. build := testBuild(1) c.client2.UploadBuild(build) crash := testCrashWithRepro(build, 1) c.client2.ReportCrash(crash) c.client2.pollEmailBug() // Receive the JobBisectCause. resp := c.client2.pollJobs(build.Manager) c.client2.expectNE(resp.ID, "") c.client2.expectEQ(resp.Type, dashapi.JobBisectCause) done := &dashapi.JobDoneReq{ ID: resp.ID, Error: []byte("testBisectFixJob:JobBisectCause"), } c.client2.expectOK(c.client2.JobDone(done)) // Ensure no more jobs. resp = c.client2.pollJobs(build.Manager) c.client2.expectEQ(resp.ID, "") // Advance time by 30 days and read out any notification emails. { c.advanceTime(30 * 24 * time.Hour) msg := c.client2.pollEmailBug() c.expectEQ(msg.Subject, "title1") c.expectTrue(strings.Contains(msg.Body, "Sending this report to the next reporting stage.")) msg = c.client2.pollEmailBug() c.expectEQ(msg.Subject, "[syzbot] title1") c.expectTrue(strings.Contains(msg.Body, "syzbot found the following issue")) } // Ensure that we get a JobBisectFix. resp = c.client2.pollJobs(build.Manager) c.client2.expectNE(resp.ID, "") c.client2.expectEQ(resp.Type, dashapi.JobBisectFix) done = &dashapi.JobDoneReq{ ID: resp.ID, Error: []byte("testBisectFixJob:JobBisectFix"), } c.client2.expectOK(c.client2.JobDone(done)) } // Test that JobBisectFix jobs are re-tried if crash occurs on ToT. func TestBisectFixRetry(t *testing.T) { c := NewCtx(t) defer c.Close() // Upload a crash report. build := testBuild(1) c.client2.UploadBuild(build) crash := testCrashWithRepro(build, 1) c.client2.ReportCrash(crash) c.client2.pollEmailBug() // Receive the JobBisectCause. resp := c.client2.pollJobs(build.Manager) c.client2.expectNE(resp.ID, "") c.client2.expectEQ(resp.Type, dashapi.JobBisectCause) done := &dashapi.JobDoneReq{ ID: resp.ID, Error: []byte("testBisectFixRetry:JobBisectCause"), } c.client2.expectOK(c.client2.JobDone(done)) // Advance time by 30 days and read out any notification emails. { c.advanceTime(30 * 24 * time.Hour) msg := c.client2.pollEmailBug() c.expectEQ(msg.Subject, "title1") c.expectTrue(strings.Contains(msg.Body, "Sending this report to the next reporting stage.")) msg = c.client2.pollEmailBug() c.expectEQ(msg.Subject, "[syzbot] title1") c.expectTrue(strings.Contains(msg.Body, "syzbot found the following issue")) } // Ensure that we get a JobBisectFix. We send back a crashlog, no error, no commits. resp = c.client2.pollJobs(build.Manager) c.client2.expectNE(resp.ID, "") c.client2.expectEQ(resp.Type, dashapi.JobBisectFix) done = &dashapi.JobDoneReq{ Build: dashapi.Build{ ID: "build1", }, ID: resp.ID, CrashLog: []byte("this is a crashlog"), CrashReport: []byte("this is a crashreport"), } c.client2.expectOK(c.client2.JobDone(done)) // Advance time by 30 days. No notification emails. { c.advanceTime(30 * 24 * time.Hour) } // Ensure that we get a JobBisectFix retry. resp = c.client2.pollJobs(build.Manager) c.client2.expectNE(resp.ID, "") c.client2.expectEQ(resp.Type, dashapi.JobBisectFix) done = &dashapi.JobDoneReq{ ID: resp.ID, Error: []byte("testBisectFixRetry:JobBisectFix"), } c.client2.expectOK(c.client2.JobDone(done)) } // Test that bisection results are not reported for bugs that are already marked as fixed. func TestNotReportingAlreadyFixed(t *testing.T) { c := NewCtx(t) defer c.Close() // Upload a crash report. build := testBuild(1) c.client2.UploadBuild(build) crash := testCrashWithRepro(build, 1) c.client2.ReportCrash(crash) c.client2.pollEmailBug() // Receive the JobBisectCause. resp := c.client2.pollJobs(build.Manager) c.client2.expectNE(resp.ID, "") c.client2.expectEQ(resp.Type, dashapi.JobBisectCause) done := &dashapi.JobDoneReq{ ID: resp.ID, Error: []byte("testBisectFixRetry:JobBisectCause"), } c.client2.expectOK(c.client2.JobDone(done)) sender := "" // Advance time by 30 days and read out any notification emails. { c.advanceTime(30 * 24 * time.Hour) msg := c.client2.pollEmailBug() c.expectEQ(msg.Subject, "title1") c.expectTrue(strings.Contains(msg.Body, "Sending this report to the next reporting stage.")) msg = c.client2.pollEmailBug() c.expectEQ(msg.Subject, "[syzbot] title1") c.expectTrue(strings.Contains(msg.Body, "syzbot found the following issue")) sender = msg.Sender } // Poll for a BisectFix job. resp = c.client2.pollJobs(build.Manager) c.client2.expectNE(resp.ID, "") c.client2.expectEQ(resp.Type, dashapi.JobBisectFix) // Meanwhile, the bug is marked as fixed separately. c.incomingEmail(sender, "#syz fix: kernel: add a fix", EmailOptCC(nil)) { // Email notification of "Your 'fix:' command is accepted, but please keep // bugs@syzkaller.com mailing list in CC next time." c.client2.pollEmailBug() } // At this point, send back the results for the BisectFix job also. done = &dashapi.JobDoneReq{ ID: resp.ID, Build: *build, Log: []byte("bisectfix log 4"), CrashTitle: "bisectfix crash title 4", CrashLog: []byte("bisectfix crash log 4"), CrashReport: []byte("bisectfix crash report 4"), Commits: []dashapi.Commit{ { Hash: "46e65cb4a0448942ec316b24d60446bbd5cc7827", Title: "kernel: add a fix", Author: "author@kernel.org", AuthorName: "Author Kernelov", CC: []string{ "reviewer1@kernel.org", "\"Reviewer2\" ", // These must be filtered out: "syzbot@testapp.appspotmail.com", "syzbot+1234@testapp.appspotmail.com", "\"syzbot\" ", }, Date: time.Date(2000, 2, 9, 4, 5, 6, 7, time.UTC), }, }, } c.expectOK(c.client2.JobDone(done)) // No reporting should come in at this point. If there is reporting, c.Close() // will fail. } // Test that fix bisections are listed on the bug page if the bug.BisectFix // is not BisectYes. func TestFixBisectionsListed(t *testing.T) { c := NewCtx(t) defer c.Close() // Upload a crash report. build := testBuild(1) c.client2.UploadBuild(build) crash := testCrashWithRepro(build, 1) c.client2.ReportCrash(crash) c.client2.pollEmailBug() // Receive the JobBisectCause. resp := c.client2.pollJobs(build.Manager) c.client2.expectNE(resp.ID, "") c.client2.expectEQ(resp.Type, dashapi.JobBisectCause) done := &dashapi.JobDoneReq{ ID: resp.ID, Error: []byte("testBisectFixRetry:JobBisectCause"), } c.client2.expectOK(c.client2.JobDone(done)) // At this point, no fix bisections should be listed out. var bugs []*Bug keys, err := db.NewQuery("Bug").GetAll(c.ctx, &bugs) c.expectEQ(err, nil) c.expectEQ(len(bugs), 1) url := fmt.Sprintf("/bug?id=%v", keys[0].StringID()) content, err := c.GET(url) c.expectEQ(err, nil) c.expectTrue(!bytes.Contains(content, []byte("All fix bisections"))) // Advance time by 30 days and read out any notification emails. { c.advanceTime(30 * 24 * time.Hour) msg := c.client2.pollEmailBug() c.expectEQ(msg.Subject, "title1") c.expectTrue(strings.Contains(msg.Body, "Sending this report to the next reporting stage.")) msg = c.client2.pollEmailBug() c.expectEQ(msg.Subject, "[syzbot] title1") c.expectTrue(strings.Contains(msg.Body, "syzbot found the following issue")) } // Ensure that we get a JobBisectFix. We send back a crashlog, no error, // no commits. resp = c.client2.pollJobs(build.Manager) c.client2.expectNE(resp.ID, "") c.client2.expectEQ(resp.Type, dashapi.JobBisectFix) done = &dashapi.JobDoneReq{ Build: dashapi.Build{ ID: "build1", }, ID: resp.ID, CrashTitle: "this is a crashtitle", CrashLog: []byte("this is a crashlog"), CrashReport: []byte("this is a crashreport"), Log: []byte("this is a log"), } c.client2.expectOK(c.client2.JobDone(done)) // Check the bug page and ensure that a bisection is listed out. content, err = c.GET(url) c.expectEQ(err, nil) c.expectTrue(bytes.Contains(content, []byte("Fix bisection attempts"))) // Advance time by 30 days. No notification emails. { c.advanceTime(30 * 24 * time.Hour) } // Ensure that we get a JobBisectFix retry. resp = c.client2.pollJobs(build.Manager) c.client2.expectNE(resp.ID, "") c.client2.expectEQ(resp.Type, dashapi.JobBisectFix) done = &dashapi.JobDoneReq{ ID: resp.ID, Error: []byte("testBisectFixRetry:JobBisectFix"), } c.client2.expectOK(c.client2.JobDone(done)) // Check the bug page and ensure that no bisections are listed out. content, err = c.GET(url) c.expectEQ(err, nil) c.expectTrue(!bytes.Contains(content, []byte("All fix bisections"))) } // Test that fix bisections do not occur if Repo has NoFixBisections set. func TestFixBisectionsDisabled(t *testing.T) { c := NewCtx(t) defer c.Close() // Upload a crash report. build := testBuild(1) build.Manager = noFixBisectionManager c.client2.UploadBuild(build) crash := testCrashWithRepro(build, 20) c.client2.ReportCrash(crash) c.client2.pollEmailBug() // Receive the JobBisectCause. resp := c.client2.pollJobs(build.Manager) c.client2.expectNE(resp.ID, "") c.client2.expectEQ(resp.Type, dashapi.JobBisectCause) done := &dashapi.JobDoneReq{ ID: resp.ID, Error: []byte("testBisectFixRetry:JobBisectCause"), } c.client2.expectOK(c.client2.JobDone(done)) // Advance time by 30 days and read out any notification emails. { c.advanceTime(30 * 24 * time.Hour) msg := c.client2.pollEmailBug() c.expectEQ(msg.Subject, "title20") c.expectTrue(strings.Contains(msg.Body, "Sending this report to the next reporting stage.")) msg = c.client2.pollEmailBug() c.expectEQ(msg.Subject, "[syzbot] title20") c.expectTrue(strings.Contains(msg.Body, "syzbot found the following issue")) } // Ensure that we do not get a JobBisectFix. resp = c.client2.pollJobs(build.Manager) c.client2.expectEQ(resp.ID, "") } func TestExternalPatchFlow(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.client build := testBuild(1) client.UploadBuild(build) crash := testCrash(build, 2) crash.Title = testErrorTitle client.ReportCrash(crash) // Confirm the report. reports, err := client.ReportingPollBugs("test") origReport := reports.Reports[0] c.expectOK(err) c.expectEQ(len(reports.Reports), 1) reply, _ := client.ReportingUpdate(&dashapi.BugUpdate{ ID: origReport.ID, Status: dashapi.BugStatusOpen, }) client.expectEQ(reply.Error, false) client.expectEQ(reply.OK, true) // Create a new patch testing job. ret, err := client.NewTestJob(&dashapi.TestPatchRequest{ BugID: origReport.ID, Link: "http://some-link.com/", User: "developer@kernel.org", Branch: "kernel-branch", Repo: "git://git.git/git.git", Patch: []byte(sampleGitPatch), }) c.expectOK(err) c.expectEQ(ret.ErrorText, "") // Make sure the job will be passed to the job processor. pollResp := c.client2.pollJobs(build.Manager) c.expectEQ(pollResp.Type, dashapi.JobTestPatch) c.expectEQ(pollResp.KernelRepo, "git://git.git/git.git") c.expectEQ(pollResp.KernelBranch, "kernel-branch") c.expectEQ(pollResp.Patch, []byte(sampleGitPatch)) // Emulate the completion of the job. build2 := testBuild(2) jobDoneReq := &dashapi.JobDoneReq{ ID: pollResp.ID, Build: *build2, CrashTitle: "test crash title", CrashLog: []byte("test crash log"), CrashReport: []byte("test crash report"), } err = c.client2.JobDone(jobDoneReq) c.expectOK(err) // Verify that we do get the bug update about the completed request. jobDoneUpdates, err := client.ReportingPollBugs("test") c.expectOK(err) c.expectEQ(len(jobDoneUpdates.Reports), 1) newReport := jobDoneUpdates.Reports[0] c.expectEQ(newReport.Type, dashapi.ReportTestPatch) c.expectEQ(newReport.CrashTitle, "test crash title") c.expectEQ(newReport.Report, []byte("test crash report")) // Confirm the patch testing result. reply, _ = client.ReportingUpdate(&dashapi.BugUpdate{ ID: origReport.ID, JobID: pollResp.ID, Status: dashapi.BugStatusOpen, }) client.expectEQ(reply.Error, false) client.expectEQ(reply.OK, true) } func TestExternalPatchTestError(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.client build := testBuild(1) client.UploadBuild(build) crash := testCrash(build, 2) crash.Title = testErrorTitle client.ReportCrash(crash) // Confirm the report. reports, err := client.ReportingPollBugs("test") origReport := reports.Reports[0] c.expectOK(err) c.expectEQ(len(reports.Reports), 1) reply, _ := client.ReportingUpdate(&dashapi.BugUpdate{ ID: origReport.ID, Status: dashapi.BugStatusOpen, }) client.expectEQ(reply.Error, false) client.expectEQ(reply.OK, true) // Create a new patch testing job. ret, err := client.NewTestJob(&dashapi.TestPatchRequest{ BugID: origReport.ID, User: "developer@kernel.org", Branch: "kernel-branch", Repo: "invalid-repo", Patch: []byte(sampleGitPatch), }) c.expectOK(err) c.expectEQ(ret.ErrorText, `"invalid-repo" does not look like a valid git repo address.`) } func TestExternalPatchCompletion(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.client build := testBuild(1) build.KernelRepo = "git://git.git/git.git" client.UploadBuild(build) crash := testCrash(build, 2) crash.Title = testErrorTitle client.ReportCrash(crash) // Confirm the report. reports, err := client.ReportingPollBugs("test") origReport := reports.Reports[0] c.expectOK(err) c.expectEQ(len(reports.Reports), 1) reply, _ := client.ReportingUpdate(&dashapi.BugUpdate{ ID: origReport.ID, Status: dashapi.BugStatusOpen, }) client.expectEQ(reply.Error, false) client.expectEQ(reply.OK, true) // Create a new patch testing job. ret, err := client.NewTestJob(&dashapi.TestPatchRequest{ BugID: origReport.ID, User: "developer@kernel.org", Patch: []byte(sampleGitPatch), }) c.expectOK(err) c.expectEQ(ret.ErrorText, "") // Make sure branch and repo are correct. pollResp := c.client2.pollJobs(build.Manager) c.expectEQ(pollResp.KernelRepo, build.KernelRepo) c.expectEQ(pollResp.KernelBranch, build.KernelBranch) } func TestParallelJobs(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.client build := testBuild(1) client.UploadBuild(build) crash := testCrash(build, 2) crash.Title = testErrorTitle client.ReportCrash(crash) // Confirm the report. reports, err := client.ReportingPollBugs("test") origReport := reports.Reports[0] c.expectOK(err) c.expectEQ(len(reports.Reports), 1) reply, _ := client.ReportingUpdate(&dashapi.BugUpdate{ ID: origReport.ID, Status: dashapi.BugStatusOpen, }) client.expectEQ(reply.Error, false) client.expectEQ(reply.OK, true) // Create a patch testing job. const ( repo1 = "git://git.git/git1.git" repo2 = "git://git.git/git2.git" ) testPatchReq := &dashapi.TestPatchRequest{ BugID: origReport.ID, Link: "http://some-link.com/", User: "developer@kernel.org", Branch: "kernel-branch", Repo: repo1, Patch: []byte(sampleGitPatch), } ret, err := client.NewTestJob(testPatchReq) c.expectOK(err) c.expectEQ(ret.ErrorText, "") // Make sure the job will be passed to the job processor. pollResp := client.pollJobs(build.Manager) c.expectEQ(pollResp.Type, dashapi.JobTestPatch) c.expectEQ(pollResp.KernelRepo, repo1) // This job is already taken, there are no other jobs. emptyPollResp := client.pollJobs(build.Manager) c.expectEQ(emptyPollResp, &dashapi.JobPollResp{}) // Create another job. testPatchReq.Repo = repo2 ret, err = client.NewTestJob(testPatchReq) c.expectOK(err) c.expectEQ(ret.ErrorText, "") // Make sure the new job will be passed to the job processor. pollResp = client.pollJobs(build.Manager) c.expectEQ(pollResp.Type, dashapi.JobTestPatch) c.expectEQ(pollResp.KernelRepo, repo2) // .. and then there'll be no other jobs. emptyPollResp = client.pollJobs(build.Manager) c.expectEQ(emptyPollResp, &dashapi.JobPollResp{}) // Emulate a syz-ci restart. client.JobReset(&dashapi.JobResetReq{Managers: []string{build.Manager}}) // .. and re-query both jobs. repos := []string{} for i := 0; i < 2; i++ { pollResp = client.pollJobs(build.Manager) c.expectEQ(pollResp.Type, dashapi.JobTestPatch) repos = append(repos, pollResp.KernelRepo) } assert.ElementsMatch(t, repos, []string{repo1, repo2}, "two patch testing requests are expected") // .. but nothing else is to be expected. emptyPollResp = client.pollJobs(build.Manager) c.expectEQ(emptyPollResp, &dashapi.JobPollResp{}) // Emulate the job's completion. build2 := testBuild(2) jobDoneReq := &dashapi.JobDoneReq{ ID: pollResp.ID, Build: *build2, CrashTitle: "test crash title", CrashLog: []byte("test crash log"), CrashReport: []byte("test crash report"), } err = client.JobDone(jobDoneReq) c.expectOK(err) client.pollBugs(1) // .. and make sure it doesn't appear again. emptyPollResp = client.pollJobs(build.Manager) c.expectEQ(emptyPollResp, &dashapi.JobPollResp{}) } // Test that JobBisectCause jobs are re-tried if there were infra problems. func TestJobCauseRetry(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.client2 // Upload a crash report. build := testBuild(1) client.UploadBuild(build) crash := testCrashWithRepro(build, 1) client.ReportCrash(crash) client.pollEmailBug() // Release the report to the second stage. c.advanceTime(15 * 24 * time.Hour) client.pollEmailBug() // "Sending report to the next stage" email. client.pollEmailBug() // New report. // Emulate an infra failure. resp := client.pollSpecificJobs(build.Manager, dashapi.ManagerJobs{ BisectCause: true, }) client.expectNE(resp.ID, "") client.expectEQ(resp.Type, dashapi.JobBisectCause) done := &dashapi.JobDoneReq{ ID: resp.ID, Error: []byte("infra problem"), Flags: dashapi.BisectResultInfraError, } client.expectOK(client.JobDone(done)) c.expectNoEmail() // Ensure we don't recreate the job right away. c.advanceTime(24 * time.Hour) resp = client.pollSpecificJobs(build.Manager, dashapi.ManagerJobs{ BisectCause: true, }) client.expectEQ(resp.ID, "") // Wait the end of the freeze period. c.advanceTime(7 * 24 * time.Hour) resp = client.pollSpecificJobs(build.Manager, dashapi.ManagerJobs{ BisectCause: true, }) client.expectNE(resp.ID, "") client.expectEQ(resp.Type, dashapi.JobBisectCause) done = &dashapi.JobDoneReq{ ID: resp.ID, Build: *testBuild(2), Log: []byte("bisect log"), CrashTitle: "bisect crash title", CrashLog: []byte("bisect crash log"), CrashReport: []byte("bisect crash report"), Commits: []dashapi.Commit{ { Hash: "36e65cb4a0448942ec316b24d60446bbd5cc7827", Title: "kernel: add a bug", Author: "author@kernel.org", CC: []string{"user@domain.com"}, Date: time.Date(2000, 2, 9, 4, 5, 6, 7, time.UTC), }, }, } done.Build.ID = resp.ID c.expectOK(client.JobDone(done)) msg := c.pollEmailBug() c.expectTrue(strings.Contains(msg.Body, "syzbot has bisected this issue to:")) } // Test that we accept `#syz test` commands without arguments. func TestEmailTestCommandNoArgs(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.publicClient build := testBuild(1) build.KernelRepo = "git://git.git/git.git" build.KernelBranch = "kernel-branch" client.UploadBuild(build) crash := testCrashWithRepro(build, 2) client.ReportCrash(crash) sender := c.pollEmailBug().Sender mailingList := c.config().Namespaces["access-public-email"].Reporting[0].Config.(*EmailConfig).Email c.incomingEmail(sender, "#syz test\n"+sampleGitPatch, EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList})) c.expectNoEmail() pollResp := client.pollJobs(build.Manager) c.expectEQ(pollResp.Type, dashapi.JobTestPatch) c.expectEQ(pollResp.KernelRepo, build.KernelRepo) c.expectEQ(pollResp.KernelBranch, build.KernelBranch) c.expectEQ(pollResp.Patch, []byte(sampleGitPatch)) } func TestAliasPatchTestingJob(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.client build := testBuild(1) client.UploadBuild(build) crash := testCrash(build, 2) crash.Title = testErrorTitle client.ReportCrash(crash) // Confirm the report. reports, err := client.ReportingPollBugs("test") origReport := reports.Reports[0] c.expectOK(err) reply, _ := client.ReportingUpdate(&dashapi.BugUpdate{ ID: origReport.ID, Status: dashapi.BugStatusOpen, }) client.expectEQ(reply.Error, false) client.expectEQ(reply.OK, true) // Create a new patch testing job. _, err = client.NewTestJob(&dashapi.TestPatchRequest{ BugID: origReport.ID, User: "developer@kernel.org", Branch: "some-branch", Repo: "repo10alias", Patch: []byte(sampleGitPatch), }) c.expectOK(err) // Make sure branch and repo are correct. pollResp := c.client2.pollJobs(build.Manager) c.expectEQ(pollResp.KernelRepo, "git://syzkaller.org") c.expectEQ(pollResp.KernelBranch, "some-branch") }