github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/dashboard/app/jobs_test.go (about)

     1  // Copyright 2017 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package main
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/google/syzkaller/dashboard/dashapi"
    14  	"github.com/google/syzkaller/pkg/email"
    15  	"github.com/stretchr/testify/assert"
    16  	db "google.golang.org/appengine/v2/datastore"
    17  )
    18  
    19  const sampleGitPatch = `--- a/mm/kasan/kasan.c
    20  +++ b/mm/kasan/kasan.c
    21  -       current->kasan_depth++;
    22  +       current->kasan_depth--;
    23  `
    24  
    25  const syzTestGitBranchSamplePatch = "#syz test: git://git.git/git.git kernel-branch\n" + sampleGitPatch
    26  
    27  // nolint: funlen
    28  func TestJob(t *testing.T) {
    29  	c := NewCtx(t)
    30  	defer c.Close()
    31  
    32  	client := c.publicClient
    33  	build := testBuild(1)
    34  	client.UploadBuild(build)
    35  
    36  	// Report crash without repro, check that test requests are not accepted.
    37  	crash := testCrash(build, 1)
    38  	crash.Maintainers = []string{"maintainer@kernel.org"}
    39  	client.ReportCrash(crash)
    40  
    41  	sender := c.pollEmailBug().Sender
    42  	c.incomingEmail(sender, "#syz upstream\n")
    43  	sender = c.pollEmailBug().Sender
    44  	_, extBugID, err := email.RemoveAddrContext(sender)
    45  	c.expectOK(err)
    46  	mailingList := c.config().Namespaces["access-public-email"].Reporting[0].Config.(*EmailConfig).Email
    47  	c.incomingEmail(sender, "bla-bla-bla", EmailOptFrom("maintainer@kernel.org"),
    48  		EmailOptCC([]string{mailingList, "kernel@mailing.list"}))
    49  
    50  	c.incomingEmail(sender, syzTestGitBranchSamplePatch,
    51  		EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList}))
    52  	body := c.pollEmailBug().Body
    53  	t.Logf("body: %s", body)
    54  	c.expectEQ(strings.Contains(body, "This crash does not have a reproducer"), true)
    55  
    56  	// Report crash with repro.
    57  	crash.ReproOpts = []byte("repro opts")
    58  	crash.ReproSyz = []byte("repro syz")
    59  	crash.ReproC = []byte("repro C")
    60  	client.ReportCrash(crash)
    61  	client.pollAndFailBisectJob(build.Manager)
    62  
    63  	body = c.pollEmailBug().Body
    64  	c.expectEQ(strings.Contains(body, "syzbot has found a reproducer"), true)
    65  
    66  	c.incomingEmail(sender, "#syz test: repo",
    67  		EmailOptFrom("test@requester.com"), EmailOptSubject("my-subject"), EmailOptCC([]string{mailingList}))
    68  	msg := c.pollEmailBug()
    69  	c.expectEQ(strings.Contains(msg.Body, "want either no args or 2 args"), true)
    70  	c.expectEQ(msg.Subject, "Re: my-subject")
    71  
    72  	c.incomingEmail(sender, "#syz test: repo branch commit",
    73  		EmailOptFrom("test@requester.com"), EmailOptSubject("Re: my-subject"), EmailOptCC([]string{mailingList}))
    74  	msg = c.pollEmailBug()
    75  	c.expectEQ(strings.Contains(msg.Body, "want either no args or 2 args"), true)
    76  	c.expectEQ(msg.Subject, "Re: my-subject")
    77  
    78  	c.incomingEmail(sender, "#syz test: repo branch",
    79  		EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList}))
    80  	body = c.pollEmailBug().Body
    81  	c.expectEQ(strings.Contains(body, "does not look like a valid git repo"), true)
    82  
    83  	c.incomingEmail(sender, syzTestGitBranchSamplePatch,
    84  		EmailOptFrom("\"foo\" <blOcKed@dOmain.COM>"))
    85  	c.expectNoEmail()
    86  	pollResp := client.pollJobs(build.Manager)
    87  	c.expectEQ(pollResp.ID, "")
    88  
    89  	// This submits actual test request.
    90  	c.incomingEmail(sender, syzTestGitBranchSamplePatch,
    91  		EmailOptMessageID(1), EmailOptFrom("test@requester.com"),
    92  		EmailOptCC([]string{"somebody@else.com", "test@syzkaller.com"}))
    93  	c.expectNoEmail()
    94  
    95  	// A dup of the same request with the same Message-ID.
    96  	c.incomingEmail(sender, syzTestGitBranchSamplePatch,
    97  		EmailOptMessageID(1), EmailOptFrom("test@requester.com"),
    98  		EmailOptCC([]string{"somebody@else.com", "test@syzkaller.com"}))
    99  	c.expectNoEmail()
   100  
   101  	pollResp = client.pollJobs("foobar")
   102  	c.expectEQ(pollResp.ID, "")
   103  	pollResp = client.pollJobs(build.Manager)
   104  	c.expectNE(pollResp.ID, "")
   105  	c.expectEQ(pollResp.Type, dashapi.JobTestPatch)
   106  	c.expectEQ(pollResp.Manager, build.Manager)
   107  	c.expectEQ(pollResp.KernelRepo, "git://git.git/git.git")
   108  	c.expectEQ(pollResp.KernelBranch, "kernel-branch")
   109  	c.expectEQ(pollResp.KernelConfig, build.KernelConfig)
   110  	c.expectEQ(pollResp.SyzkallerCommit, build.SyzkallerCommit)
   111  	c.expectEQ(pollResp.Patch, []byte(sampleGitPatch))
   112  	c.expectEQ(pollResp.ReproOpts, []byte("repro opts"))
   113  	c.expectEQ(pollResp.ReproSyz, []byte(
   114  		"# See https://goo.gl/kgGztJ for information about syzkaller reproducers.\n"+
   115  			"#repro opts\n"+
   116  			"repro syz"))
   117  	c.expectEQ(pollResp.ReproC, []byte("repro C"))
   118  
   119  	jobDoneReq := &dashapi.JobDoneReq{
   120  		ID:          pollResp.ID,
   121  		Build:       *build,
   122  		CrashTitle:  "test crash title",
   123  		CrashLog:    []byte("test crash log"),
   124  		CrashReport: []byte("test crash report"),
   125  	}
   126  	client.JobDone(jobDoneReq)
   127  
   128  	{
   129  		dbJob, dbBuild, _ := c.loadJob(pollResp.ID)
   130  		patchLink := externalLink(c.ctx, textPatch, dbJob.Patch)
   131  		kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig)
   132  		logLink := externalLink(c.ctx, textCrashLog, dbJob.CrashLog)
   133  		msg := c.pollEmailBug()
   134  		to := email.MergeEmailLists([]string{"test@requester.com", "somebody@else.com", mailingList})
   135  		c.expectEQ(msg.To, to)
   136  		c.expectEQ(msg.Subject, "Re: [syzbot] "+crash.Title)
   137  		c.expectEQ(len(msg.Attachments), 0)
   138  		c.expectEQ(msg.Body, fmt.Sprintf(`Hello,
   139  
   140  syzbot has tested the proposed patch but the reproducer is still triggering an issue:
   141  test crash title
   142  
   143  test crash report
   144  
   145  Tested on:
   146  
   147  commit:         11111111 kernel_commit_title1
   148  git tree:       repo1 branch1
   149  console output: %[3]v
   150  kernel config:  %[2]v
   151  dashboard link: https://testapp.appspot.com/bug?extid=%[4]v
   152  compiler:       compiler1
   153  patch:          %[1]v
   154  
   155  `, patchLink, kernelConfigLink, logLink, extBugID))
   156  		c.checkURLContents(patchLink, []byte(sampleGitPatch))
   157  		c.checkURLContents(kernelConfigLink, build.KernelConfig)
   158  		c.checkURLContents(logLink, jobDoneReq.CrashLog)
   159  	}
   160  
   161  	// Testing fails with an error.
   162  	c.incomingEmail(sender, syzTestGitBranchSamplePatch, EmailOptMessageID(2))
   163  	pollResp = client.pollJobs(build.Manager)
   164  	c.expectEQ(pollResp.Type, dashapi.JobTestPatch)
   165  	jobDoneReq = &dashapi.JobDoneReq{
   166  		ID:    pollResp.ID,
   167  		Build: *build,
   168  		Error: []byte("failed to apply patch"),
   169  	}
   170  	client.JobDone(jobDoneReq)
   171  	{
   172  		dbJob, dbBuild, _ := c.loadJob(pollResp.ID)
   173  		patchLink := externalLink(c.ctx, textPatch, dbJob.Patch)
   174  		kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig)
   175  		msg := c.pollEmailBug()
   176  		c.expectEQ(len(msg.Attachments), 0)
   177  		c.expectEQ(msg.Body, fmt.Sprintf(`Hello,
   178  
   179  syzbot tried to test the proposed patch but the build/boot failed:
   180  
   181  failed to apply patch
   182  
   183  
   184  Tested on:
   185  
   186  commit:         11111111 kernel_commit_title1
   187  git tree:       repo1 branch1
   188  kernel config:  %[2]v
   189  dashboard link: https://testapp.appspot.com/bug?extid=%[3]v
   190  compiler:       compiler1
   191  patch:          %[1]v
   192  
   193  `, patchLink, kernelConfigLink, extBugID))
   194  		c.checkURLContents(patchLink, []byte(sampleGitPatch))
   195  		c.checkURLContents(kernelConfigLink, build.KernelConfig)
   196  	}
   197  
   198  	// Testing fails with a huge error that can't be inlined in email.
   199  	c.incomingEmail(sender, syzTestGitBranchSamplePatch, EmailOptMessageID(3))
   200  	pollResp = client.pollJobs(build.Manager)
   201  	c.expectEQ(pollResp.Type, dashapi.JobTestPatch)
   202  	jobDoneReq = &dashapi.JobDoneReq{
   203  		ID:    pollResp.ID,
   204  		Build: *build,
   205  		Error: bytes.Repeat([]byte{'a', 'b', 'c'}, (maxInlineError+100)/3),
   206  	}
   207  	client.JobDone(jobDoneReq)
   208  	{
   209  		dbJob, dbBuild, _ := c.loadJob(pollResp.ID)
   210  		patchLink := externalLink(c.ctx, textPatch, dbJob.Patch)
   211  		errorLink := externalLink(c.ctx, textError, dbJob.Error)
   212  		kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig)
   213  		msg := c.pollEmailBug()
   214  		c.expectEQ(len(msg.Attachments), 0)
   215  		truncatedError := string(jobDoneReq.Error[len(jobDoneReq.Error)-maxInlineError:])
   216  		c.expectEQ(msg.Body, fmt.Sprintf(`Hello,
   217  
   218  syzbot tried to test the proposed patch but the build/boot failed:
   219  
   220  %[1]v
   221  
   222  Error text is too large and was truncated, full error text is at:
   223  %[2]v
   224  
   225  
   226  Tested on:
   227  
   228  commit:         11111111 kernel_commit_title1
   229  git tree:       repo1 branch1
   230  kernel config:  %[4]v
   231  dashboard link: https://testapp.appspot.com/bug?extid=%[5]v
   232  compiler:       compiler1
   233  patch:          %[3]v
   234  
   235  `, truncatedError, errorLink, patchLink, kernelConfigLink, extBugID))
   236  		c.checkURLContents(patchLink, []byte(sampleGitPatch))
   237  		c.checkURLContents(errorLink, jobDoneReq.Error)
   238  		c.checkURLContents(kernelConfigLink, build.KernelConfig)
   239  	}
   240  
   241  	c.incomingEmail(sender, syzTestGitBranchSamplePatch, EmailOptMessageID(4))
   242  	pollResp = client.pollJobs(build.Manager)
   243  	c.expectEQ(pollResp.Type, dashapi.JobTestPatch)
   244  	jobDoneReq = &dashapi.JobDoneReq{
   245  		ID:       pollResp.ID,
   246  		Build:    *build,
   247  		CrashLog: []byte("console output"),
   248  	}
   249  	client.JobDone(jobDoneReq)
   250  	{
   251  		dbJob, dbBuild, _ := c.loadJob(pollResp.ID)
   252  		patchLink := externalLink(c.ctx, textPatch, dbJob.Patch)
   253  		logLink := externalLink(c.ctx, textCrashLog, dbJob.CrashLog)
   254  		kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig)
   255  		msg := c.pollEmailBug()
   256  		c.expectEQ(len(msg.Attachments), 0)
   257  		c.expectEQ(msg.Body, fmt.Sprintf(`Hello,
   258  
   259  syzbot has tested the proposed patch and the reproducer did not trigger any issue:
   260  
   261  Reported-and-tested-by: syzbot+%v@testapp.appspotmail.com
   262  
   263  Tested on:
   264  
   265  commit:         11111111 kernel_commit_title1
   266  git tree:       repo1 branch1
   267  console output: %[4]v
   268  kernel config:  %[3]v
   269  dashboard link: https://testapp.appspot.com/bug?extid=%[1]v
   270  compiler:       compiler1
   271  patch:          %[2]v
   272  
   273  Note: testing is done by a robot and is best-effort only.
   274  `, extBugID, patchLink, kernelConfigLink, logLink))
   275  		c.checkURLContents(patchLink, []byte(sampleGitPatch))
   276  		c.checkURLContents(kernelConfigLink, build.KernelConfig)
   277  	}
   278  
   279  	pollResp = client.pollJobs(build.Manager)
   280  	c.expectEQ(pollResp.ID, "")
   281  }
   282  
   283  // Test whether we can test boot time crashes.
   284  func TestBootErrorPatch(t *testing.T) {
   285  	c := NewCtx(t)
   286  	defer c.Close()
   287  
   288  	build := testBuild(1)
   289  	c.client2.UploadBuild(build)
   290  
   291  	crash := testCrash(build, 2)
   292  	crash.Title = "riscv/fixes boot error: can't ssh into the instance"
   293  	c.client2.ReportCrash(crash)
   294  
   295  	report := c.pollEmailBug()
   296  	c.incomingEmail(report.Sender, "#syz upstream\n", EmailOptCC(report.To))
   297  	report = c.pollEmailBug()
   298  
   299  	c.incomingEmail(report.Sender, syzTestGitBranchSamplePatch,
   300  		EmailOptFrom("test@requester.com"), EmailOptCC(report.To))
   301  	c.expectNoEmail()
   302  	pollResp := c.client2.pollJobs(build.Manager)
   303  	c.expectEQ(pollResp.Type, dashapi.JobTestPatch)
   304  }
   305  
   306  const testErrorTitle = `upstream test error: WARNING in __queue_work`
   307  
   308  func TestTestErrorPatch(t *testing.T) {
   309  	c := NewCtx(t)
   310  	defer c.Close()
   311  
   312  	build := testBuild(1)
   313  	c.client2.UploadBuild(build)
   314  
   315  	crash := testCrash(build, 2)
   316  	crash.Title = testErrorTitle
   317  	c.client2.ReportCrash(crash)
   318  
   319  	sender := c.pollEmailBug().Sender
   320  	c.incomingEmail(sender, "#syz upstream\n")
   321  	report := c.pollEmailBug()
   322  
   323  	c.incomingEmail(report.Sender, syzTestGitBranchSamplePatch,
   324  		EmailOptFrom("test@requester.com"), EmailOptCC(report.To))
   325  	c.expectNoEmail()
   326  	pollResp := c.client2.pollJobs(build.Manager)
   327  	c.expectEQ(pollResp.Type, dashapi.JobTestPatch)
   328  }
   329  
   330  // Test on particular commit and without a patch.
   331  func TestJobWithoutPatch(t *testing.T) {
   332  	c := NewCtx(t)
   333  	defer c.Close()
   334  
   335  	client := c.publicClient
   336  
   337  	build := testBuild(1)
   338  	client.UploadBuild(build)
   339  
   340  	crash := testCrash(build, 1)
   341  	crash.ReproOpts = []byte("repro opts")
   342  	crash.ReproSyz = []byte("repro syz")
   343  	client.ReportCrash(crash)
   344  	client.pollAndFailBisectJob(build.Manager)
   345  	sender := c.pollEmailBug().Sender
   346  	_, extBugID, err := email.RemoveAddrContext(sender)
   347  	c.expectOK(err)
   348  
   349  	// Patch testing should happen for bugs with fix commits too.
   350  	c.incomingEmail(sender, "#syz fix: some commit title\n")
   351  
   352  	c.incomingEmail(sender, "#syz test git://mygit.com/git.git 5e6a2eea\n", EmailOptMessageID(1))
   353  	c.expectNoEmail()
   354  	pollResp := client.pollJobs(build.Manager)
   355  	c.expectNE(pollResp.ID, "")
   356  	c.expectEQ(pollResp.Type, dashapi.JobTestPatch)
   357  	testBuild := testBuild(2)
   358  	testBuild.KernelRepo = "git://mygit.com/git.git"
   359  	testBuild.KernelBranch = ""
   360  	testBuild.KernelCommit = "5e6a2eea5e6a2eea5e6a2eea5e6a2eea5e6a2eea"
   361  	jobDoneReq := &dashapi.JobDoneReq{
   362  		ID:    pollResp.ID,
   363  		Build: *testBuild,
   364  	}
   365  	client.JobDone(jobDoneReq)
   366  	{
   367  		_, dbBuild, _ := c.loadJob(pollResp.ID)
   368  		kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig)
   369  		msg := c.pollEmailBug()
   370  		c.expectEQ(len(msg.Attachments), 0)
   371  		c.expectEQ(msg.Body, fmt.Sprintf(`Hello,
   372  
   373  syzbot has tested the proposed patch and the reproducer did not trigger any issue:
   374  
   375  Reported-and-tested-by: syzbot+%v@testapp.appspotmail.com
   376  
   377  Tested on:
   378  
   379  commit:         5e6a2eea kernel_commit_title2
   380  git tree:       git://mygit.com/git.git
   381  kernel config:  %[2]v
   382  dashboard link: https://testapp.appspot.com/bug?extid=%[1]v
   383  compiler:       compiler2
   384  
   385  Note: no patches were applied.
   386  Note: testing is done by a robot and is best-effort only.
   387  `, extBugID, kernelConfigLink))
   388  		c.checkURLContents(kernelConfigLink, testBuild.KernelConfig)
   389  	}
   390  
   391  	pollResp = client.pollJobs(build.Manager)
   392  	c.expectEQ(pollResp.ID, "")
   393  }
   394  
   395  func TestReproRetestJob(t *testing.T) {
   396  	c := NewCtx(t)
   397  	defer c.Close()
   398  
   399  	client := c.publicClient
   400  	oldBuild := testBuild(1)
   401  	oldBuild.KernelRepo = "git://mygit.com/git.git"
   402  	oldBuild.KernelBranch = "main"
   403  	client.UploadBuild(oldBuild)
   404  
   405  	crash := testCrash(oldBuild, 1)
   406  	crash.ReproOpts = []byte("repro opts")
   407  	crash.ReproSyz = []byte("repro syz")
   408  	client.ReportCrash(crash)
   409  	sender := c.pollEmailBug().Sender
   410  	_, extBugID, err := email.RemoveAddrContext(sender)
   411  	c.expectOK(err)
   412  
   413  	crash2 := testCrash(oldBuild, 1)
   414  	crash2.ReproOpts = []byte("repro opts")
   415  	crash2.ReproSyz = []byte("repro syz")
   416  	crash2.ReproC = []byte("repro C")
   417  	client.ReportCrash(crash2)
   418  	c.pollEmailBug()
   419  
   420  	// Upload a newer build.
   421  	c.advanceTime(time.Minute)
   422  	build := testBuild(1)
   423  	build.ID = "new-build"
   424  	build.KernelRepo = "git://mygit.com/new-git.git"
   425  	build.KernelBranch = "new-main"
   426  	build.KernelConfig = []byte{0xAB, 0xCD, 0xEF}
   427  	client.UploadBuild(build)
   428  
   429  	c.advanceTime(time.Hour)
   430  	bug, _, _ := c.loadBug(extBugID)
   431  	c.expectEQ(bug.ReproLevel, ReproLevelC)
   432  
   433  	// Let's say that the C repro testing has failed.
   434  	c.advanceTime(c.config().Obsoleting.ReproRetestStart + time.Hour)
   435  	for i := 0; i < 2; i++ {
   436  		resp := client.pollSpecificJobs(build.Manager, dashapi.ManagerJobs{TestPatches: true})
   437  		c.expectEQ(resp.Type, dashapi.JobTestPatch)
   438  		c.expectEQ(resp.KernelRepo, build.KernelRepo)
   439  		c.expectEQ(resp.KernelBranch, build.KernelBranch)
   440  		c.expectEQ(resp.KernelConfig, build.KernelConfig)
   441  		c.expectEQ(resp.Patch, []uint8(nil))
   442  		var done *dashapi.JobDoneReq
   443  		if resp.ReproC == nil {
   444  			// Pretend that the syz repro still works.
   445  			done = &dashapi.JobDoneReq{
   446  				ID:          resp.ID,
   447  				CrashTitle:  crash.Title,
   448  				CrashLog:    []byte("test crash log"),
   449  				CrashReport: []byte("test crash report"),
   450  			}
   451  		} else {
   452  			// Pretend that the C repro fails.
   453  			done = &dashapi.JobDoneReq{
   454  				ID: resp.ID,
   455  			}
   456  		}
   457  		client.expectOK(client.JobDone(done))
   458  	}
   459  	// Expect that the repro level is no longer ReproLevelC.
   460  	c.expectNoEmail()
   461  	bug, _, _ = c.loadBug(extBugID)
   462  	c.expectEQ(bug.HeadReproLevel, ReproLevelSyz)
   463  	// Let's also deprecate the syz repro.
   464  	c.advanceTime(c.config().Obsoleting.ReproRetestPeriod + time.Hour)
   465  
   466  	resp := client.pollSpecificJobs(build.Manager, dashapi.ManagerJobs{TestPatches: true})
   467  	c.expectEQ(resp.Type, dashapi.JobTestPatch)
   468  	c.expectEQ(resp.KernelBranch, build.KernelBranch)
   469  	c.expectEQ(resp.ReproC, []uint8(nil))
   470  	c.expectEQ(resp.KernelConfig, build.KernelConfig)
   471  	done := &dashapi.JobDoneReq{
   472  		ID: resp.ID,
   473  	}
   474  	client.expectOK(client.JobDone(done))
   475  	// Expect that the repro level is no longer ReproLevelC.
   476  	bug, _, _ = c.loadBug(extBugID)
   477  	c.expectEQ(bug.HeadReproLevel, ReproLevelNone)
   478  	c.expectEQ(bug.ReproLevel, ReproLevelC)
   479  	// Expect that the bug gets deprecated.
   480  	notif := c.pollEmailBug()
   481  	if !strings.Contains(notif.Body, "Auto-closing this bug as obsolete") {
   482  		t.Fatalf("bad notification text: %q", notif.Body)
   483  	}
   484  	// Expect that the right obsoletion reason was set.
   485  	bug, _, _ = c.loadBug(extBugID)
   486  	c.expectEQ(bug.StatusReason, dashapi.InvalidatedByRevokedRepro)
   487  }
   488  
   489  func TestDelegatedManagerReproRetest(t *testing.T) {
   490  	c := NewCtx(t)
   491  	defer c.Close()
   492  
   493  	client := c.makeClient(clientMgrDecommission, keyMgrDecommission, true)
   494  	oldManager := notYetDecommManger
   495  	newManager := delegateToManager
   496  
   497  	oldBuild := testBuild(1)
   498  	oldBuild.KernelRepo = "git://delegated.repo/git.git"
   499  	oldBuild.KernelBranch = "main"
   500  	oldBuild.Manager = oldManager
   501  	client.UploadBuild(oldBuild)
   502  
   503  	crash := testCrash(oldBuild, 1)
   504  	crash.ReproOpts = []byte("repro opts")
   505  	crash.ReproSyz = []byte("repro syz")
   506  	crash.ReproC = []byte("repro C")
   507  	client.ReportCrash(crash)
   508  	sender := c.pollEmailBug().Sender
   509  	_, extBugID, err := email.RemoveAddrContext(sender)
   510  	c.expectOK(err)
   511  
   512  	// Deprecate the oldManager.
   513  	c.decommissionManager("test-mgr-decommission", oldManager, newManager)
   514  
   515  	// Upload a build for the new manager.
   516  	c.advanceTime(time.Minute)
   517  	build := testBuild(1)
   518  	build.ID = "new-build"
   519  	build.KernelRepo = "git://delegated.repo/new-git.git"
   520  	build.KernelBranch = "new-main"
   521  	build.KernelConfig = []byte{0xAB, 0xCD, 0xEF}
   522  	build.Manager = newManager
   523  	client.UploadBuild(build)
   524  
   525  	// Wait until the bug is upstreamed.
   526  	c.advanceTime(20 * 24 * time.Hour)
   527  	c.pollEmailBug()
   528  	c.pollEmailBug()
   529  
   530  	// Let's say that the C repro testing has failed.
   531  	c.advanceTime(c.config().Obsoleting.ReproRetestPeriod + time.Hour)
   532  
   533  	resp := client.pollSpecificJobs(build.Manager, dashapi.ManagerJobs{TestPatches: true})
   534  	c.expectEQ(resp.Type, dashapi.JobTestPatch)
   535  	c.expectEQ(resp.KernelRepo, build.KernelRepo)
   536  	c.expectEQ(resp.KernelBranch, build.KernelBranch)
   537  	c.expectEQ(resp.KernelConfig, build.KernelConfig)
   538  	c.expectEQ(resp.Patch, []uint8(nil))
   539  
   540  	// Pretend that the C repro fails.
   541  	done := &dashapi.JobDoneReq{
   542  		ID: resp.ID,
   543  	}
   544  
   545  	client.expectOK(client.JobDone(done))
   546  
   547  	// If it has worked, the repro is revoked and the bug is obsoleted.
   548  	c.pollEmailBug()
   549  	bug, _, _ := c.loadBug(extBugID)
   550  	c.expectEQ(bug.HeadReproLevel, ReproLevelNone)
   551  }
   552  
   553  // Test on a restricted manager.
   554  func TestJobRestrictedManager(t *testing.T) {
   555  	c := NewCtx(t)
   556  	defer c.Close()
   557  
   558  	client := c.publicClient
   559  
   560  	build := testBuild(1)
   561  	build.Manager = restrictedManager
   562  	client.UploadBuild(build)
   563  
   564  	crash := testCrash(build, 1)
   565  	crash.ReproSyz = []byte("repro syz")
   566  	client.ReportCrash(crash)
   567  	client.pollAndFailBisectJob(build.Manager)
   568  	sender := c.pollEmailBug().Sender
   569  
   570  	// Testing on a wrong repo must fail and no test jobs passed to manager.
   571  	c.incomingEmail(sender, "#syz test: git://mygit.com/git.git master\n", EmailOptMessageID(1))
   572  	reply := c.pollEmailBug()
   573  	c.expectEQ(strings.Contains(reply.Body, "you should test only on restricted.git"), true)
   574  	pollResp := client.pollJobs(build.Manager)
   575  	c.expectEQ(pollResp.ID, "")
   576  
   577  	// Testing on the right repo must succeed.
   578  	c.incomingEmail(sender, "#syz test: git://restricted.git/restricted.git master\n", EmailOptMessageID(2))
   579  	pollResp = client.pollJobs(build.Manager)
   580  	c.expectNE(pollResp.ID, "")
   581  	c.expectEQ(pollResp.Type, dashapi.JobTestPatch)
   582  	c.expectEQ(pollResp.Manager, build.Manager)
   583  	c.expectEQ(pollResp.KernelRepo, "git://restricted.git/restricted.git")
   584  }
   585  
   586  // Test that JobBisectFix is returned only after 30 days.
   587  func TestBisectFixJob(t *testing.T) {
   588  	c := NewCtx(t)
   589  	defer c.Close()
   590  
   591  	// Upload a crash report.
   592  	build := testBuild(1)
   593  	c.client2.UploadBuild(build)
   594  	crash := testCrashWithRepro(build, 1)
   595  	c.client2.ReportCrash(crash)
   596  	c.client2.pollEmailBug()
   597  
   598  	// Receive the JobBisectCause.
   599  	resp := c.client2.pollJobs(build.Manager)
   600  	c.client2.expectNE(resp.ID, "")
   601  	c.client2.expectEQ(resp.Type, dashapi.JobBisectCause)
   602  	done := &dashapi.JobDoneReq{
   603  		ID:    resp.ID,
   604  		Error: []byte("testBisectFixJob:JobBisectCause"),
   605  	}
   606  	c.client2.expectOK(c.client2.JobDone(done))
   607  
   608  	// Ensure no more jobs.
   609  	resp = c.client2.pollJobs(build.Manager)
   610  	c.client2.expectEQ(resp.ID, "")
   611  
   612  	// Advance time by 30 days and read out any notification emails.
   613  	{
   614  		c.advanceTime(30 * 24 * time.Hour)
   615  		msg := c.client2.pollEmailBug()
   616  		c.expectEQ(msg.Subject, "title1")
   617  		c.expectTrue(strings.Contains(msg.Body, "Sending this report to the next reporting stage."))
   618  
   619  		msg = c.client2.pollEmailBug()
   620  		c.expectEQ(msg.Subject, "[syzbot] title1")
   621  		c.expectTrue(strings.Contains(msg.Body, "syzbot found the following issue"))
   622  	}
   623  
   624  	// Ensure that we get a JobBisectFix.
   625  	resp = c.client2.pollJobs(build.Manager)
   626  	c.client2.expectNE(resp.ID, "")
   627  	c.client2.expectEQ(resp.Type, dashapi.JobBisectFix)
   628  	done = &dashapi.JobDoneReq{
   629  		ID:    resp.ID,
   630  		Error: []byte("testBisectFixJob:JobBisectFix"),
   631  	}
   632  	c.client2.expectOK(c.client2.JobDone(done))
   633  }
   634  
   635  // Test that JobBisectFix jobs are re-tried if crash occurs on ToT.
   636  func TestBisectFixRetry(t *testing.T) {
   637  	c := NewCtx(t)
   638  	defer c.Close()
   639  
   640  	// Upload a crash report.
   641  	build := testBuild(1)
   642  	c.client2.UploadBuild(build)
   643  	crash := testCrashWithRepro(build, 1)
   644  	c.client2.ReportCrash(crash)
   645  	c.client2.pollEmailBug()
   646  
   647  	// Receive the JobBisectCause.
   648  	resp := c.client2.pollJobs(build.Manager)
   649  	c.client2.expectNE(resp.ID, "")
   650  	c.client2.expectEQ(resp.Type, dashapi.JobBisectCause)
   651  	done := &dashapi.JobDoneReq{
   652  		ID:    resp.ID,
   653  		Error: []byte("testBisectFixRetry:JobBisectCause"),
   654  	}
   655  	c.client2.expectOK(c.client2.JobDone(done))
   656  
   657  	// Advance time by 30 days and read out any notification emails.
   658  	{
   659  		c.advanceTime(30 * 24 * time.Hour)
   660  		msg := c.client2.pollEmailBug()
   661  		c.expectEQ(msg.Subject, "title1")
   662  		c.expectTrue(strings.Contains(msg.Body, "Sending this report to the next reporting stage."))
   663  
   664  		msg = c.client2.pollEmailBug()
   665  		c.expectEQ(msg.Subject, "[syzbot] title1")
   666  		c.expectTrue(strings.Contains(msg.Body, "syzbot found the following issue"))
   667  	}
   668  
   669  	// Ensure that we get a JobBisectFix. We send back a crashlog, no error, no commits.
   670  	resp = c.client2.pollJobs(build.Manager)
   671  	c.client2.expectNE(resp.ID, "")
   672  	c.client2.expectEQ(resp.Type, dashapi.JobBisectFix)
   673  	done = &dashapi.JobDoneReq{
   674  		Build: dashapi.Build{
   675  			ID: "build1",
   676  		},
   677  		ID:          resp.ID,
   678  		CrashLog:    []byte("this is a crashlog"),
   679  		CrashReport: []byte("this is a crashreport"),
   680  	}
   681  	c.client2.expectOK(c.client2.JobDone(done))
   682  
   683  	// Advance time by 30 days. No notification emails.
   684  	{
   685  		c.advanceTime(30 * 24 * time.Hour)
   686  	}
   687  
   688  	// Ensure that we get a JobBisectFix retry.
   689  	resp = c.client2.pollJobs(build.Manager)
   690  	c.client2.expectNE(resp.ID, "")
   691  	c.client2.expectEQ(resp.Type, dashapi.JobBisectFix)
   692  	done = &dashapi.JobDoneReq{
   693  		ID:    resp.ID,
   694  		Error: []byte("testBisectFixRetry:JobBisectFix"),
   695  	}
   696  	c.client2.expectOK(c.client2.JobDone(done))
   697  }
   698  
   699  // Test that bisection results are not reported for bugs that are already marked as fixed.
   700  func TestNotReportingAlreadyFixed(t *testing.T) {
   701  	c := NewCtx(t)
   702  	defer c.Close()
   703  
   704  	// Upload a crash report.
   705  	build := testBuild(1)
   706  	c.client2.UploadBuild(build)
   707  	crash := testCrashWithRepro(build, 1)
   708  	c.client2.ReportCrash(crash)
   709  	c.client2.pollEmailBug()
   710  
   711  	// Receive the JobBisectCause.
   712  	resp := c.client2.pollJobs(build.Manager)
   713  	c.client2.expectNE(resp.ID, "")
   714  	c.client2.expectEQ(resp.Type, dashapi.JobBisectCause)
   715  	done := &dashapi.JobDoneReq{
   716  		ID:    resp.ID,
   717  		Error: []byte("testBisectFixRetry:JobBisectCause"),
   718  	}
   719  	c.client2.expectOK(c.client2.JobDone(done))
   720  
   721  	sender := ""
   722  	// Advance time by 30 days and read out any notification emails.
   723  	{
   724  		c.advanceTime(30 * 24 * time.Hour)
   725  		msg := c.client2.pollEmailBug()
   726  		c.expectEQ(msg.Subject, "title1")
   727  		c.expectTrue(strings.Contains(msg.Body, "Sending this report to the next reporting stage."))
   728  
   729  		msg = c.client2.pollEmailBug()
   730  		c.expectEQ(msg.Subject, "[syzbot] title1")
   731  		c.expectTrue(strings.Contains(msg.Body, "syzbot found the following issue"))
   732  		sender = msg.Sender
   733  	}
   734  
   735  	// Poll for a BisectFix job.
   736  	resp = c.client2.pollJobs(build.Manager)
   737  	c.client2.expectNE(resp.ID, "")
   738  	c.client2.expectEQ(resp.Type, dashapi.JobBisectFix)
   739  
   740  	// Meanwhile, the bug is marked as fixed separately.
   741  	c.incomingEmail(sender, "#syz fix: kernel: add a fix", EmailOptCC(nil))
   742  
   743  	{
   744  		// Email notification of "Your 'fix:' command is accepted, but please keep
   745  		// bugs@syzkaller.com mailing list in CC next time."
   746  		c.client2.pollEmailBug()
   747  	}
   748  
   749  	// At this point, send back the results for the BisectFix job also.
   750  	done = &dashapi.JobDoneReq{
   751  		ID:          resp.ID,
   752  		Build:       *build,
   753  		Log:         []byte("bisectfix log 4"),
   754  		CrashTitle:  "bisectfix crash title 4",
   755  		CrashLog:    []byte("bisectfix crash log 4"),
   756  		CrashReport: []byte("bisectfix crash report 4"),
   757  		Commits: []dashapi.Commit{
   758  			{
   759  				Hash:       "46e65cb4a0448942ec316b24d60446bbd5cc7827",
   760  				Title:      "kernel: add a fix",
   761  				Author:     "author@kernel.org",
   762  				AuthorName: "Author Kernelov",
   763  				CC: []string{
   764  					"reviewer1@kernel.org", "\"Reviewer2\" <reviewer2@kernel.org>",
   765  					// These must be filtered out:
   766  					"syzbot@testapp.appspotmail.com",
   767  					"syzbot+1234@testapp.appspotmail.com",
   768  					"\"syzbot\" <syzbot+1234@testapp.appspotmail.com>",
   769  				},
   770  				Date: time.Date(2000, 2, 9, 4, 5, 6, 7, time.UTC),
   771  			},
   772  		},
   773  	}
   774  	c.expectOK(c.client2.JobDone(done))
   775  
   776  	// No reporting should come in at this point. If there is reporting, c.Close()
   777  	// will fail.
   778  }
   779  
   780  // Test that fix bisections are listed on the bug page if the bug.BisectFix
   781  // is not BisectYes.
   782  func TestFixBisectionsListed(t *testing.T) {
   783  	c := NewCtx(t)
   784  	defer c.Close()
   785  
   786  	// Upload a crash report.
   787  	build := testBuild(1)
   788  	c.client2.UploadBuild(build)
   789  	crash := testCrashWithRepro(build, 1)
   790  	c.client2.ReportCrash(crash)
   791  	c.client2.pollEmailBug()
   792  
   793  	// Receive the JobBisectCause.
   794  	resp := c.client2.pollJobs(build.Manager)
   795  	c.client2.expectNE(resp.ID, "")
   796  	c.client2.expectEQ(resp.Type, dashapi.JobBisectCause)
   797  	done := &dashapi.JobDoneReq{
   798  		ID:    resp.ID,
   799  		Error: []byte("testBisectFixRetry:JobBisectCause"),
   800  	}
   801  	c.client2.expectOK(c.client2.JobDone(done))
   802  
   803  	// At this point, no fix bisections should be listed out.
   804  	var bugs []*Bug
   805  	keys, err := db.NewQuery("Bug").GetAll(c.ctx, &bugs)
   806  	c.expectEQ(err, nil)
   807  	c.expectEQ(len(bugs), 1)
   808  	url := fmt.Sprintf("/bug?id=%v", keys[0].StringID())
   809  	content, err := c.GET(url)
   810  	c.expectEQ(err, nil)
   811  	c.expectTrue(!bytes.Contains(content, []byte("All fix bisections")))
   812  
   813  	// Advance time by 30 days and read out any notification emails.
   814  	{
   815  		c.advanceTime(30 * 24 * time.Hour)
   816  		msg := c.client2.pollEmailBug()
   817  		c.expectEQ(msg.Subject, "title1")
   818  		c.expectTrue(strings.Contains(msg.Body, "Sending this report to the next reporting stage."))
   819  
   820  		msg = c.client2.pollEmailBug()
   821  		c.expectEQ(msg.Subject, "[syzbot] title1")
   822  		c.expectTrue(strings.Contains(msg.Body, "syzbot found the following issue"))
   823  	}
   824  
   825  	// Ensure that we get a JobBisectFix. We send back a crashlog, no error,
   826  	// no commits.
   827  	resp = c.client2.pollJobs(build.Manager)
   828  	c.client2.expectNE(resp.ID, "")
   829  	c.client2.expectEQ(resp.Type, dashapi.JobBisectFix)
   830  	done = &dashapi.JobDoneReq{
   831  		Build: dashapi.Build{
   832  			ID: "build1",
   833  		},
   834  		ID:          resp.ID,
   835  		CrashTitle:  "this is a crashtitle",
   836  		CrashLog:    []byte("this is a crashlog"),
   837  		CrashReport: []byte("this is a crashreport"),
   838  		Log:         []byte("this is a log"),
   839  	}
   840  	c.client2.expectOK(c.client2.JobDone(done))
   841  
   842  	// Check the bug page and ensure that a bisection is listed out.
   843  	content, err = c.GET(url)
   844  	c.expectEQ(err, nil)
   845  	c.expectTrue(bytes.Contains(content, []byte("Fix bisection attempts")))
   846  
   847  	// Advance time by 30 days. No notification emails.
   848  	{
   849  		c.advanceTime(30 * 24 * time.Hour)
   850  	}
   851  
   852  	// Ensure that we get a JobBisectFix retry.
   853  	resp = c.client2.pollJobs(build.Manager)
   854  	c.client2.expectNE(resp.ID, "")
   855  	c.client2.expectEQ(resp.Type, dashapi.JobBisectFix)
   856  	done = &dashapi.JobDoneReq{
   857  		ID:    resp.ID,
   858  		Error: []byte("testBisectFixRetry:JobBisectFix"),
   859  	}
   860  	c.client2.expectOK(c.client2.JobDone(done))
   861  
   862  	// Check the bug page and ensure that no bisections are listed out.
   863  	content, err = c.GET(url)
   864  	c.expectEQ(err, nil)
   865  	c.expectTrue(!bytes.Contains(content, []byte("All fix bisections")))
   866  }
   867  
   868  // Test that fix bisections do not occur if Repo has NoFixBisections set.
   869  func TestFixBisectionsDisabled(t *testing.T) {
   870  	c := NewCtx(t)
   871  	defer c.Close()
   872  
   873  	// Upload a crash report.
   874  	build := testBuild(1)
   875  	build.Manager = noFixBisectionManager
   876  	c.client2.UploadBuild(build)
   877  	crash := testCrashWithRepro(build, 20)
   878  	c.client2.ReportCrash(crash)
   879  	c.client2.pollEmailBug()
   880  
   881  	// Receive the JobBisectCause.
   882  	resp := c.client2.pollJobs(build.Manager)
   883  	c.client2.expectNE(resp.ID, "")
   884  	c.client2.expectEQ(resp.Type, dashapi.JobBisectCause)
   885  	done := &dashapi.JobDoneReq{
   886  		ID:    resp.ID,
   887  		Error: []byte("testBisectFixRetry:JobBisectCause"),
   888  	}
   889  	c.client2.expectOK(c.client2.JobDone(done))
   890  
   891  	// Advance time by 30 days and read out any notification emails.
   892  	{
   893  		c.advanceTime(30 * 24 * time.Hour)
   894  		msg := c.client2.pollEmailBug()
   895  		c.expectEQ(msg.Subject, "title20")
   896  		c.expectTrue(strings.Contains(msg.Body, "Sending this report to the next reporting stage."))
   897  
   898  		msg = c.client2.pollEmailBug()
   899  		c.expectEQ(msg.Subject, "[syzbot] title20")
   900  		c.expectTrue(strings.Contains(msg.Body, "syzbot found the following issue"))
   901  	}
   902  
   903  	// Ensure that we do not get a JobBisectFix.
   904  	resp = c.client2.pollJobs(build.Manager)
   905  	c.client2.expectEQ(resp.ID, "")
   906  }
   907  
   908  func TestExternalPatchFlow(t *testing.T) {
   909  	c := NewCtx(t)
   910  	defer c.Close()
   911  
   912  	client := c.client
   913  
   914  	build := testBuild(1)
   915  	client.UploadBuild(build)
   916  
   917  	crash := testCrash(build, 2)
   918  	crash.Title = testErrorTitle
   919  	client.ReportCrash(crash)
   920  
   921  	// Confirm the report.
   922  	reports, err := client.ReportingPollBugs("test")
   923  	origReport := reports.Reports[0]
   924  	c.expectOK(err)
   925  	c.expectEQ(len(reports.Reports), 1)
   926  
   927  	reply, _ := client.ReportingUpdate(&dashapi.BugUpdate{
   928  		ID:     origReport.ID,
   929  		Status: dashapi.BugStatusOpen,
   930  	})
   931  	client.expectEQ(reply.Error, false)
   932  	client.expectEQ(reply.OK, true)
   933  
   934  	// Create a new patch testing job.
   935  	ret, err := client.NewTestJob(&dashapi.TestPatchRequest{
   936  		BugID:  origReport.ID,
   937  		Link:   "http://some-link.com/",
   938  		User:   "developer@kernel.org",
   939  		Branch: "kernel-branch",
   940  		Repo:   "git://git.git/git.git",
   941  		Patch:  []byte(sampleGitPatch),
   942  	})
   943  	c.expectOK(err)
   944  	c.expectEQ(ret.ErrorText, "")
   945  
   946  	// Make sure the job will be passed to the job processor.
   947  	pollResp := c.client2.pollJobs(build.Manager)
   948  	c.expectEQ(pollResp.Type, dashapi.JobTestPatch)
   949  	c.expectEQ(pollResp.KernelRepo, "git://git.git/git.git")
   950  	c.expectEQ(pollResp.KernelBranch, "kernel-branch")
   951  	c.expectEQ(pollResp.Patch, []byte(sampleGitPatch))
   952  
   953  	// Emulate the completion of the job.
   954  	build2 := testBuild(2)
   955  	jobDoneReq := &dashapi.JobDoneReq{
   956  		ID:          pollResp.ID,
   957  		Build:       *build2,
   958  		CrashTitle:  "test crash title",
   959  		CrashLog:    []byte("test crash log"),
   960  		CrashReport: []byte("test crash report"),
   961  	}
   962  	err = c.client2.JobDone(jobDoneReq)
   963  	c.expectOK(err)
   964  
   965  	// Verify that we do get the bug update about the completed request.
   966  	jobDoneUpdates, err := client.ReportingPollBugs("test")
   967  	c.expectOK(err)
   968  	c.expectEQ(len(jobDoneUpdates.Reports), 1)
   969  
   970  	newReport := jobDoneUpdates.Reports[0]
   971  	c.expectEQ(newReport.Type, dashapi.ReportTestPatch)
   972  	c.expectEQ(newReport.CrashTitle, "test crash title")
   973  	c.expectEQ(newReport.Report, []byte("test crash report"))
   974  
   975  	// Confirm the patch testing result.
   976  	reply, _ = client.ReportingUpdate(&dashapi.BugUpdate{
   977  		ID:     origReport.ID,
   978  		JobID:  pollResp.ID,
   979  		Status: dashapi.BugStatusOpen,
   980  	})
   981  	client.expectEQ(reply.Error, false)
   982  	client.expectEQ(reply.OK, true)
   983  }
   984  
   985  func TestExternalPatchTestError(t *testing.T) {
   986  	c := NewCtx(t)
   987  	defer c.Close()
   988  
   989  	client := c.client
   990  
   991  	build := testBuild(1)
   992  	client.UploadBuild(build)
   993  
   994  	crash := testCrash(build, 2)
   995  	crash.Title = testErrorTitle
   996  	client.ReportCrash(crash)
   997  
   998  	// Confirm the report.
   999  	reports, err := client.ReportingPollBugs("test")
  1000  	origReport := reports.Reports[0]
  1001  	c.expectOK(err)
  1002  	c.expectEQ(len(reports.Reports), 1)
  1003  
  1004  	reply, _ := client.ReportingUpdate(&dashapi.BugUpdate{
  1005  		ID:     origReport.ID,
  1006  		Status: dashapi.BugStatusOpen,
  1007  	})
  1008  	client.expectEQ(reply.Error, false)
  1009  	client.expectEQ(reply.OK, true)
  1010  
  1011  	// Create a new patch testing job.
  1012  	ret, err := client.NewTestJob(&dashapi.TestPatchRequest{
  1013  		BugID:  origReport.ID,
  1014  		User:   "developer@kernel.org",
  1015  		Branch: "kernel-branch",
  1016  		Repo:   "invalid-repo",
  1017  		Patch:  []byte(sampleGitPatch),
  1018  	})
  1019  	c.expectOK(err)
  1020  	c.expectEQ(ret.ErrorText, `"invalid-repo" does not look like a valid git repo address.`)
  1021  }
  1022  
  1023  func TestExternalPatchCompletion(t *testing.T) {
  1024  	c := NewCtx(t)
  1025  	defer c.Close()
  1026  
  1027  	client := c.client
  1028  
  1029  	build := testBuild(1)
  1030  	build.KernelRepo = "git://git.git/git.git"
  1031  	client.UploadBuild(build)
  1032  
  1033  	crash := testCrash(build, 2)
  1034  	crash.Title = testErrorTitle
  1035  	client.ReportCrash(crash)
  1036  
  1037  	// Confirm the report.
  1038  	reports, err := client.ReportingPollBugs("test")
  1039  	origReport := reports.Reports[0]
  1040  	c.expectOK(err)
  1041  	c.expectEQ(len(reports.Reports), 1)
  1042  
  1043  	reply, _ := client.ReportingUpdate(&dashapi.BugUpdate{
  1044  		ID:     origReport.ID,
  1045  		Status: dashapi.BugStatusOpen,
  1046  	})
  1047  	client.expectEQ(reply.Error, false)
  1048  	client.expectEQ(reply.OK, true)
  1049  
  1050  	// Create a new patch testing job.
  1051  	ret, err := client.NewTestJob(&dashapi.TestPatchRequest{
  1052  		BugID: origReport.ID,
  1053  		User:  "developer@kernel.org",
  1054  		Patch: []byte(sampleGitPatch),
  1055  	})
  1056  	c.expectOK(err)
  1057  	c.expectEQ(ret.ErrorText, "")
  1058  
  1059  	// Make sure branch and repo are correct.
  1060  	pollResp := c.client2.pollJobs(build.Manager)
  1061  	c.expectEQ(pollResp.KernelRepo, build.KernelRepo)
  1062  	c.expectEQ(pollResp.KernelBranch, build.KernelBranch)
  1063  }
  1064  
  1065  func TestParallelJobs(t *testing.T) {
  1066  	c := NewCtx(t)
  1067  	defer c.Close()
  1068  
  1069  	client := c.client
  1070  
  1071  	build := testBuild(1)
  1072  	client.UploadBuild(build)
  1073  
  1074  	crash := testCrash(build, 2)
  1075  	crash.Title = testErrorTitle
  1076  	client.ReportCrash(crash)
  1077  
  1078  	// Confirm the report.
  1079  	reports, err := client.ReportingPollBugs("test")
  1080  	origReport := reports.Reports[0]
  1081  	c.expectOK(err)
  1082  	c.expectEQ(len(reports.Reports), 1)
  1083  
  1084  	reply, _ := client.ReportingUpdate(&dashapi.BugUpdate{
  1085  		ID:     origReport.ID,
  1086  		Status: dashapi.BugStatusOpen,
  1087  	})
  1088  	client.expectEQ(reply.Error, false)
  1089  	client.expectEQ(reply.OK, true)
  1090  
  1091  	// Create a patch testing job.
  1092  	const (
  1093  		repo1 = "git://git.git/git1.git"
  1094  		repo2 = "git://git.git/git2.git"
  1095  	)
  1096  	testPatchReq := &dashapi.TestPatchRequest{
  1097  		BugID:  origReport.ID,
  1098  		Link:   "http://some-link.com/",
  1099  		User:   "developer@kernel.org",
  1100  		Branch: "kernel-branch",
  1101  		Repo:   repo1,
  1102  		Patch:  []byte(sampleGitPatch),
  1103  	}
  1104  	ret, err := client.NewTestJob(testPatchReq)
  1105  	c.expectOK(err)
  1106  	c.expectEQ(ret.ErrorText, "")
  1107  
  1108  	// Make sure the job will be passed to the job processor.
  1109  	pollResp := client.pollJobs(build.Manager)
  1110  	c.expectEQ(pollResp.Type, dashapi.JobTestPatch)
  1111  	c.expectEQ(pollResp.KernelRepo, repo1)
  1112  
  1113  	// This job is already taken, there are no other jobs.
  1114  	emptyPollResp := client.pollJobs(build.Manager)
  1115  	c.expectEQ(emptyPollResp, &dashapi.JobPollResp{})
  1116  
  1117  	// Create another job.
  1118  	testPatchReq.Repo = repo2
  1119  	ret, err = client.NewTestJob(testPatchReq)
  1120  	c.expectOK(err)
  1121  	c.expectEQ(ret.ErrorText, "")
  1122  
  1123  	// Make sure the new job will be passed to the job processor.
  1124  	pollResp = client.pollJobs(build.Manager)
  1125  	c.expectEQ(pollResp.Type, dashapi.JobTestPatch)
  1126  	c.expectEQ(pollResp.KernelRepo, repo2)
  1127  
  1128  	// .. and then there'll be no other jobs.
  1129  	emptyPollResp = client.pollJobs(build.Manager)
  1130  	c.expectEQ(emptyPollResp, &dashapi.JobPollResp{})
  1131  
  1132  	// Emulate a syz-ci restart.
  1133  	client.JobReset(&dashapi.JobResetReq{Managers: []string{build.Manager}})
  1134  
  1135  	// .. and re-query both jobs.
  1136  	repos := []string{}
  1137  	for i := 0; i < 2; i++ {
  1138  		pollResp = client.pollJobs(build.Manager)
  1139  		c.expectEQ(pollResp.Type, dashapi.JobTestPatch)
  1140  		repos = append(repos, pollResp.KernelRepo)
  1141  	}
  1142  	assert.ElementsMatch(t, repos, []string{repo1, repo2}, "two patch testing requests are expected")
  1143  
  1144  	// .. but nothing else is to be expected.
  1145  	emptyPollResp = client.pollJobs(build.Manager)
  1146  	c.expectEQ(emptyPollResp, &dashapi.JobPollResp{})
  1147  
  1148  	// Emulate the job's completion.
  1149  	build2 := testBuild(2)
  1150  	jobDoneReq := &dashapi.JobDoneReq{
  1151  		ID:          pollResp.ID,
  1152  		Build:       *build2,
  1153  		CrashTitle:  "test crash title",
  1154  		CrashLog:    []byte("test crash log"),
  1155  		CrashReport: []byte("test crash report"),
  1156  	}
  1157  	err = client.JobDone(jobDoneReq)
  1158  	c.expectOK(err)
  1159  	client.pollBugs(1)
  1160  
  1161  	// .. and make sure it doesn't appear again.
  1162  	emptyPollResp = client.pollJobs(build.Manager)
  1163  	c.expectEQ(emptyPollResp, &dashapi.JobPollResp{})
  1164  }
  1165  
  1166  // Test that JobBisectCause jobs are re-tried if there were infra problems.
  1167  func TestJobCauseRetry(t *testing.T) {
  1168  	c := NewCtx(t)
  1169  	defer c.Close()
  1170  
  1171  	client := c.client2
  1172  	// Upload a crash report.
  1173  	build := testBuild(1)
  1174  	client.UploadBuild(build)
  1175  	crash := testCrashWithRepro(build, 1)
  1176  	client.ReportCrash(crash)
  1177  	client.pollEmailBug()
  1178  
  1179  	// Release the report to the second stage.
  1180  	c.advanceTime(15 * 24 * time.Hour)
  1181  	client.pollEmailBug() // "Sending report to the next stage" email.
  1182  	client.pollEmailBug() // New report.
  1183  
  1184  	// Emulate an infra failure.
  1185  	resp := client.pollSpecificJobs(build.Manager, dashapi.ManagerJobs{
  1186  		BisectCause: true,
  1187  	})
  1188  	client.expectNE(resp.ID, "")
  1189  	client.expectEQ(resp.Type, dashapi.JobBisectCause)
  1190  	done := &dashapi.JobDoneReq{
  1191  		ID:    resp.ID,
  1192  		Error: []byte("infra problem"),
  1193  		Flags: dashapi.BisectResultInfraError,
  1194  	}
  1195  	client.expectOK(client.JobDone(done))
  1196  	c.expectNoEmail()
  1197  
  1198  	// Ensure we don't recreate the job right away.
  1199  	c.advanceTime(24 * time.Hour)
  1200  	resp = client.pollSpecificJobs(build.Manager, dashapi.ManagerJobs{
  1201  		BisectCause: true,
  1202  	})
  1203  	client.expectEQ(resp.ID, "")
  1204  
  1205  	// Wait the end of the freeze period.
  1206  	c.advanceTime(7 * 24 * time.Hour)
  1207  	resp = client.pollSpecificJobs(build.Manager, dashapi.ManagerJobs{
  1208  		BisectCause: true,
  1209  	})
  1210  	client.expectNE(resp.ID, "")
  1211  	client.expectEQ(resp.Type, dashapi.JobBisectCause)
  1212  
  1213  	done = &dashapi.JobDoneReq{
  1214  		ID:          resp.ID,
  1215  		Build:       *testBuild(2),
  1216  		Log:         []byte("bisect log"),
  1217  		CrashTitle:  "bisect crash title",
  1218  		CrashLog:    []byte("bisect crash log"),
  1219  		CrashReport: []byte("bisect crash report"),
  1220  		Commits: []dashapi.Commit{
  1221  			{
  1222  				Hash:   "36e65cb4a0448942ec316b24d60446bbd5cc7827",
  1223  				Title:  "kernel: add a bug",
  1224  				Author: "author@kernel.org",
  1225  				CC:     []string{"user@domain.com"},
  1226  				Date:   time.Date(2000, 2, 9, 4, 5, 6, 7, time.UTC),
  1227  			},
  1228  		},
  1229  	}
  1230  	done.Build.ID = resp.ID
  1231  	c.expectOK(client.JobDone(done))
  1232  
  1233  	msg := c.pollEmailBug()
  1234  	c.expectTrue(strings.Contains(msg.Body, "syzbot has bisected this issue to:"))
  1235  }
  1236  
  1237  // Test that we accept `#syz test` commands without arguments.
  1238  func TestEmailTestCommandNoArgs(t *testing.T) {
  1239  	c := NewCtx(t)
  1240  	defer c.Close()
  1241  
  1242  	client := c.publicClient
  1243  	build := testBuild(1)
  1244  	build.KernelRepo = "git://git.git/git.git"
  1245  	build.KernelBranch = "kernel-branch"
  1246  	client.UploadBuild(build)
  1247  
  1248  	crash := testCrashWithRepro(build, 2)
  1249  	client.ReportCrash(crash)
  1250  
  1251  	sender := c.pollEmailBug().Sender
  1252  	mailingList := c.config().Namespaces["access-public-email"].Reporting[0].Config.(*EmailConfig).Email
  1253  
  1254  	c.incomingEmail(sender, "#syz test\n"+sampleGitPatch,
  1255  		EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList}))
  1256  	c.expectNoEmail()
  1257  	pollResp := client.pollJobs(build.Manager)
  1258  	c.expectEQ(pollResp.Type, dashapi.JobTestPatch)
  1259  	c.expectEQ(pollResp.KernelRepo, build.KernelRepo)
  1260  	c.expectEQ(pollResp.KernelBranch, build.KernelBranch)
  1261  	c.expectEQ(pollResp.Patch, []byte(sampleGitPatch))
  1262  }
  1263  
  1264  func TestAliasPatchTestingJob(t *testing.T) {
  1265  	c := NewCtx(t)
  1266  	defer c.Close()
  1267  
  1268  	client := c.client
  1269  
  1270  	build := testBuild(1)
  1271  	client.UploadBuild(build)
  1272  
  1273  	crash := testCrash(build, 2)
  1274  	crash.Title = testErrorTitle
  1275  	client.ReportCrash(crash)
  1276  
  1277  	// Confirm the report.
  1278  	reports, err := client.ReportingPollBugs("test")
  1279  	origReport := reports.Reports[0]
  1280  	c.expectOK(err)
  1281  
  1282  	reply, _ := client.ReportingUpdate(&dashapi.BugUpdate{
  1283  		ID:     origReport.ID,
  1284  		Status: dashapi.BugStatusOpen,
  1285  	})
  1286  	client.expectEQ(reply.Error, false)
  1287  	client.expectEQ(reply.OK, true)
  1288  
  1289  	// Create a new patch testing job.
  1290  	_, err = client.NewTestJob(&dashapi.TestPatchRequest{
  1291  		BugID:  origReport.ID,
  1292  		User:   "developer@kernel.org",
  1293  		Branch: "some-branch",
  1294  		Repo:   "repo10alias",
  1295  		Patch:  []byte(sampleGitPatch),
  1296  	})
  1297  	c.expectOK(err)
  1298  
  1299  	// Make sure branch and repo are correct.
  1300  	pollResp := c.client2.pollJobs(build.Manager)
  1301  	c.expectEQ(pollResp.KernelRepo, "git://syzkaller.org")
  1302  	c.expectEQ(pollResp.KernelBranch, "some-branch")
  1303  }