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