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

     1  // Copyright 2023 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  	"fmt"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/google/syzkaller/dashboard/dashapi"
    13  	"github.com/google/syzkaller/pkg/email"
    14  	"github.com/google/syzkaller/pkg/subsystem"
    15  	"github.com/stretchr/testify/assert"
    16  )
    17  
    18  const subsystemTestNs = "test1"
    19  
    20  func TestSubsytemMaintainers(t *testing.T) {
    21  	c := NewCtx(t)
    22  	defer c.Close()
    23  
    24  	// This also indirectly tests getSubsystemService.
    25  	assert.ElementsMatch(t,
    26  		subsystemMaintainers(c.ctx, subsystemTestNs, "subsystemA"),
    27  		[]string{
    28  			"subsystemA@list.com", "subsystemA@person.com",
    29  		},
    30  	)
    31  	assert.ElementsMatch(t, subsystemMaintainers(c.ctx, subsystemTestNs, "does-not-exist"), []string{})
    32  }
    33  
    34  func TestPeriodicSubsystemRefresh(t *testing.T) {
    35  	c := NewCtx(t)
    36  	defer c.Close()
    37  
    38  	client := c.client
    39  	ns := subsystemTestNs
    40  
    41  	build := testBuild(1)
    42  	client.UploadBuild(build)
    43  
    44  	// Create a bug without any subsystems.
    45  	c.setSubsystems(ns, nil, 1)
    46  	crash := testCrash(build, 1)
    47  	crash.Title = "WARNING: abcd"
    48  	crash.GuiltyFiles = []string{"test.c"}
    49  	client.ReportCrash(crash)
    50  	rep := client.pollBug()
    51  	extID := rep.ID
    52  
    53  	// Initially there should be no subsystems.
    54  	expectLabels(t, client, extID)
    55  
    56  	// Update subsystems.
    57  	item := &subsystem.Subsystem{
    58  		Name:      "first",
    59  		PathRules: []subsystem.PathRule{{IncludeRegexp: `test\.c`}},
    60  	}
    61  	// Keep revision the same.
    62  	c.setSubsystems(ns, []*subsystem.Subsystem{item}, 1)
    63  
    64  	// Refresh subsystems.
    65  	c.advanceTime(time.Hour)
    66  	_, err := c.AuthGET(AccessUser, "/cron/refresh_subsystems")
    67  	c.expectOK(err)
    68  	expectLabels(t, client, extID) // Not enough time has passed yet.
    69  
    70  	// Wait until the refresh period is over.
    71  	c.advanceTime(openBugsUpdateTime)
    72  
    73  	_, err = c.AuthGET(AccessUser, "/cron/refresh_subsystems")
    74  	c.expectOK(err)
    75  	expectLabels(t, client, extID, "subsystems:first")
    76  }
    77  
    78  func TestOpenBugRevRefresh(t *testing.T) {
    79  	c := NewCtx(t)
    80  	defer c.Close()
    81  
    82  	client := c.client
    83  	ns := subsystemTestNs
    84  
    85  	build := testBuild(1)
    86  	client.UploadBuild(build)
    87  
    88  	// Create a bug without any subsystems.
    89  	c.setSubsystems(ns, nil, 0)
    90  	crash := testCrash(build, 1)
    91  	crash.GuiltyFiles = []string{"test.c"}
    92  	client.ReportCrash(crash)
    93  	rep := client.pollBug()
    94  	extID := rep.ID
    95  
    96  	// Initially there should be no subsystems.
    97  	expectLabels(t, client, extID)
    98  
    99  	// Update subsystems.
   100  	c.advanceTime(time.Hour)
   101  	item := &subsystem.Subsystem{
   102  		Name:      "first",
   103  		PathRules: []subsystem.PathRule{{IncludeRegexp: `test\.c`}},
   104  	}
   105  	// Update the revision number as well.
   106  	c.setSubsystems(ns, []*subsystem.Subsystem{item}, 1)
   107  
   108  	// Refresh subsystems.
   109  	_, err := c.AuthGET(AccessUser, "/cron/refresh_subsystems")
   110  	c.expectOK(err)
   111  	expectLabels(t, client, extID, "subsystems:first")
   112  }
   113  
   114  func TestClosedBugSubsystemRefresh(t *testing.T) {
   115  	c := NewCtx(t)
   116  	defer c.Close()
   117  
   118  	client := c.client
   119  	ns := subsystemTestNs
   120  
   121  	build := testBuild(1)
   122  	client.UploadBuild(build)
   123  
   124  	// Create a bug without any subsystems.
   125  	c.setSubsystems(ns, nil, 0)
   126  	crash := testCrash(build, 1)
   127  	crash.GuiltyFiles = []string{"test.c"}
   128  	client.ReportCrash(crash)
   129  	rep := client.pollBug()
   130  	extID := rep.ID
   131  
   132  	// "Fix" the bug.
   133  	reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{
   134  		ID:         rep.ID,
   135  		Status:     dashapi.BugStatusOpen,
   136  		FixCommits: []string{"foo: fix the crash"},
   137  	})
   138  	c.expectEQ(reply.OK, true)
   139  	build2 := testBuild(2)
   140  	build2.Manager = build.Manager
   141  	build2.Commits = []string{"foo: fix the crash"}
   142  	client.UploadBuild(build2)
   143  	client.pollNotifs(0)
   144  	bug, _, _ := c.loadBug(rep.ID)
   145  	c.expectEQ(bug.Status, BugStatusFixed)
   146  
   147  	// Initially there should be no subsystems.
   148  	expectLabels(t, client, extID)
   149  
   150  	// Update subsystems.
   151  	c.advanceTime(time.Hour)
   152  	item := &subsystem.Subsystem{
   153  		Name:      "first",
   154  		PathRules: []subsystem.PathRule{{IncludeRegexp: `test\.c`}},
   155  	}
   156  	c.setSubsystems(ns, []*subsystem.Subsystem{item}, 1)
   157  
   158  	// Refresh subsystems.
   159  	c.advanceTime(time.Hour)
   160  	_, err := c.AuthGET(AccessUser, "/cron/refresh_subsystems")
   161  	c.expectOK(err)
   162  	expectLabels(t, client, extID, "subsystems:first")
   163  }
   164  
   165  func TestInvalidBugSubsystemRefresh(t *testing.T) {
   166  	c := NewCtx(t)
   167  	defer c.Close()
   168  
   169  	client := c.client
   170  	build := testBuild(1)
   171  	client.UploadBuild(build)
   172  
   173  	// Create a bug without any subsystems.
   174  	c.setSubsystems(subsystemTestNs, nil, 0)
   175  	crash := testCrash(build, 1)
   176  	crash.GuiltyFiles = []string{"test.c"}
   177  	client.ReportCrash(crash)
   178  	rep := client.pollBug()
   179  	extID := rep.ID
   180  
   181  	// Invalidate the bug.
   182  	reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{
   183  		ID:     rep.ID,
   184  		Status: dashapi.BugStatusInvalid,
   185  	})
   186  	c.expectEQ(reply.OK, true)
   187  	bug, _, _ := c.loadBug(rep.ID)
   188  	c.expectEQ(bug.Status, BugStatusInvalid)
   189  
   190  	// Initially there should be no subsystems.
   191  	expectLabels(t, client, extID)
   192  
   193  	// Update subsystems.
   194  	c.advanceTime(time.Hour)
   195  	item := &subsystem.Subsystem{
   196  		Name:      "first",
   197  		PathRules: []subsystem.PathRule{{IncludeRegexp: `test\.c`}},
   198  	}
   199  	c.setSubsystems(subsystemTestNs, []*subsystem.Subsystem{item}, 1)
   200  
   201  	// Refresh subsystems.
   202  	c.advanceTime(time.Hour)
   203  	_, err := c.AuthGET(AccessUser, "/cron/refresh_subsystems")
   204  	c.expectOK(err)
   205  	expectLabels(t, client, extID, "subsystems:first")
   206  }
   207  
   208  func TestUserSubsystemsRefresh(t *testing.T) {
   209  	c := NewCtx(t)
   210  	defer c.Close()
   211  
   212  	client := c.makeClient(clientPublicEmail, keyPublicEmail, true)
   213  	ns := "access-public-email"
   214  
   215  	build := testBuild(1)
   216  	client.UploadBuild(build)
   217  
   218  	// Create a bug with subsystemA.
   219  	crash := testCrash(build, 1)
   220  	crash.GuiltyFiles = []string{"a.c"}
   221  	client.ReportCrash(crash)
   222  	c.incomingEmail(c.pollEmailBug().Sender, "#syz upstream\n")
   223  
   224  	sender := c.pollEmailBug().Sender
   225  	_, extID, err := email.RemoveAddrContext(sender)
   226  	c.expectOK(err)
   227  
   228  	// Make sure we've set the right subsystem.
   229  	expectLabels(t, client, extID, "subsystems:subsystemA")
   230  
   231  	// Manually set another subsystem.
   232  	c.incomingEmail(sender, "#syz set subsystems: subsystemB\n",
   233  		EmailOptFrom("test@requester.com"))
   234  	expectLabels(t, client, extID, "subsystems:subsystemB")
   235  
   236  	// Refresh subsystems.
   237  	c.advanceTime(openBugsUpdateTime + time.Hour)
   238  	_, err = c.AuthGET(AccessUser, "/cron/refresh_subsystems")
   239  	c.expectOK(err)
   240  
   241  	// The subsystems must stay the same.
   242  	expectLabels(t, client, extID, "subsystems:subsystemB")
   243  
   244  	// Bump the subsystem revision and refresh subsystems.
   245  	c.setSubsystems(ns, testSubsystems, 2)
   246  	c.advanceTime(time.Hour)
   247  	_, err = c.AuthGET(AccessUser, "/cron/refresh_subsystems")
   248  	c.expectOK(err)
   249  
   250  	// The subsystem must still stay the same.
   251  	expectLabels(t, client, extID, "subsystems:subsystemB")
   252  }
   253  
   254  func TestNoUserSubsystemOverwrite(t *testing.T) {
   255  	c := NewCtx(t)
   256  	defer c.Close()
   257  
   258  	client := c.makeClient(clientPublicEmail, keyPublicEmail, true)
   259  
   260  	build := testBuild(1)
   261  	client.UploadBuild(build)
   262  
   263  	// Create a bug without subsystems.
   264  	crash := testCrash(build, 1)
   265  	client.ReportCrash(crash)
   266  	c.incomingEmail(c.pollEmailBug().Sender, "#syz upstream\n")
   267  
   268  	sender := c.pollEmailBug().Sender
   269  	_, extID, err := email.RemoveAddrContext(sender)
   270  	c.expectOK(err)
   271  
   272  	// Manually set subsystemA.
   273  	c.incomingEmail(sender, "#syz set subsystems: subsystemA\n",
   274  		EmailOptFrom("test@requester.com"))
   275  	expectLabels(t, client, extID, "subsystems:subsystemA")
   276  
   277  	// Now we find a reproducer that indicates it's subsystemB.
   278  
   279  	crash.GuiltyFiles = []string{"b.c"}
   280  	crash.ReproOpts = []byte("some opts")
   281  	crash.ReproSyz = []byte("getpid()")
   282  	client.ReportCrash(crash)
   283  	c.pollEmailBug()
   284  
   285  	// Make sure subsystem stayed unchanged.
   286  	expectLabels(t, client, extID, "subsystems:subsystemA")
   287  }
   288  
   289  // nolint: goconst
   290  func TestPeriodicSubsystemReminders(t *testing.T) {
   291  	c := NewCtx(t)
   292  	defer c.Close()
   293  
   294  	client := c.makeClient(clientSubsystemRemind, keySubsystemRemind, true)
   295  	build := testBuild(1)
   296  	client.UploadBuild(build)
   297  
   298  	bugToExtID := map[string]string{}
   299  
   300  	// WARNING: a first (3 crashes)
   301  	aFirst := testCrash(build, 1)
   302  	aFirst.Title = `WARNING: a first`
   303  	aFirst.GuiltyFiles = []string{"a.c"}
   304  	client.ReportCrash(aFirst)
   305  	bugToExtID[aFirst.Title] = client.pollEmailExtID()
   306  	for i := 0; i < 2; i++ {
   307  		client.ReportCrash(aFirst)
   308  		c.advanceTime(time.Hour)
   309  	}
   310  
   311  	// WARNING: a second (1 crash)
   312  	aSecond := testCrash(build, 1)
   313  	aSecond.Title = `WARNING: a second`
   314  	aSecond.GuiltyFiles = []string{"a.c"}
   315  	client.ReportCrash(aSecond)
   316  	bugToExtID[aSecond.Title] = client.pollEmailExtID()
   317  	c.advanceTime(time.Hour)
   318  
   319  	// WARNING: b first (1 crashes)
   320  	bFirst := testCrash(build, 1)
   321  	bFirst.Title = `WARNING: b first`
   322  	bFirst.GuiltyFiles = []string{"b.c"}
   323  	client.ReportCrash(bFirst)
   324  	bugToExtID[bFirst.Title] = client.pollEmailExtID()
   325  	c.advanceTime(time.Hour)
   326  
   327  	// WARNING: b first (5 crashes)
   328  	bSecond := testCrash(build, 1)
   329  	bSecond.Title = `WARNING: b second`
   330  	bSecond.GuiltyFiles = []string{"b.c"}
   331  	client.ReportCrash(bSecond)
   332  	bugToExtID[bSecond.Title] = client.pollEmailExtID()
   333  	for i := 0; i < 4; i++ {
   334  		client.ReportCrash(bSecond)
   335  		c.advanceTime(time.Hour)
   336  	}
   337  
   338  	// Report bugs once more to pretend they're still valid.
   339  	c.advanceTime(time.Hour * 24 * 14)
   340  	client.ReportCrash(aFirst)
   341  	client.ReportCrash(bFirst)
   342  	client.ReportCrash(aSecond)
   343  	client.ReportCrash(bSecond)
   344  	c.advanceTime(time.Hour)
   345  
   346  	// Make sure we don't report crashes at other reporting stages.
   347  	crash := testCrash(build, 1)
   348  	crash.Title = `WARNING: a third, keep in moderation` // see the config in app_test.go
   349  	crash.GuiltyFiles = []string{"a.c"}
   350  	client.ReportCrash(crash)
   351  	client.pollBug()
   352  	c.advanceTime(time.Hour)
   353  
   354  	_, err := c.GET("/cron/subsystem_reports")
   355  	c.expectOK(err)
   356  
   357  	// Expect the reminder for subsystemA.
   358  	reply := client.pollEmailBug()
   359  	c.expectEQ(reply.Subject, "[moderation] Monthly subsystemA report (Jan 2000)")
   360  	c.expectEQ(reply.To, []string{"moderation@syzkaller.com"})
   361  	c.expectEQ(reply.Cc, []string(nil))
   362  	c.expectEQ(reply.Body, fmt.Sprintf(`Hello subsystemA maintainers/developers,
   363  
   364  This is a 30-day syzbot report for the subsystemA subsystem.
   365  All related reports/information can be found at:
   366  https://testapp.appspot.com/subsystem-reminders/s/subsystemA
   367  
   368  During the period, 2 new issues were detected and 0 were fixed.
   369  In total, 2 issues are still open.
   370  
   371  Some of the still happening issues:
   372  
   373  Ref Crashes Repro Title
   374  <1> 4       No    WARNING: a first
   375                    https://testapp.appspot.com/bug?extid=%[1]v
   376  <2> 2       No    WARNING: a second
   377                    https://testapp.appspot.com/bug?extid=%[2]v
   378  
   379  The report will be sent to: [subsystemA@list.com subsystemA@person.com].
   380  
   381  ---
   382  This report is generated by a bot. It may contain errors.
   383  See https://goo.gl/tpsmEJ for more information about syzbot.
   384  syzbot engineers can be reached at syzkaller@googlegroups.com.
   385  
   386  To disable reminders for individual bugs, reply with the following command:
   387  #syz set <Ref> no-reminders
   388  
   389  To change bug's subsystems, reply with:
   390  #syz set <Ref> subsystems: new-subsystem
   391  
   392  If the report looks fine to you, reply with:
   393  #syz upstream
   394  
   395  To regenerate the report, reply with:
   396  #syz regenerate
   397  
   398  You may send multiple commands in a single email message.
   399  `, bugToExtID["WARNING: a first"], bugToExtID["WARNING: a second"]))
   400  
   401  	// Expect the reminder for subsystemB.
   402  	reply = client.pollEmailBug()
   403  	c.expectEQ(reply.Subject, "[moderation] Monthly subsystemB report (Jan 2000)")
   404  	c.expectEQ(reply.To, []string{"moderation@syzkaller.com"})
   405  	c.expectEQ(reply.Cc, []string(nil))
   406  	c.expectEQ(reply.Body, fmt.Sprintf(`Hello subsystemB maintainers/developers,
   407  
   408  This is a 30-day syzbot report for the subsystemB subsystem.
   409  All related reports/information can be found at:
   410  https://testapp.appspot.com/subsystem-reminders/s/subsystemB
   411  
   412  During the period, 2 new issues were detected and 0 were fixed.
   413  In total, 2 issues are still open.
   414  
   415  Some of the still happening issues:
   416  
   417  Ref Crashes Repro Title
   418  <1> 6       No    WARNING: b second
   419                    https://testapp.appspot.com/bug?extid=%[1]v
   420  <2> 2       No    WARNING: b first
   421                    https://testapp.appspot.com/bug?extid=%[2]v
   422  
   423  The report will be sent to: [subsystemB@list.com subsystemB@person.com].
   424  
   425  ---
   426  This report is generated by a bot. It may contain errors.
   427  See https://goo.gl/tpsmEJ for more information about syzbot.
   428  syzbot engineers can be reached at syzkaller@googlegroups.com.
   429  
   430  To disable reminders for individual bugs, reply with the following command:
   431  #syz set <Ref> no-reminders
   432  
   433  To change bug's subsystems, reply with:
   434  #syz set <Ref> subsystems: new-subsystem
   435  
   436  If the report looks fine to you, reply with:
   437  #syz upstream
   438  
   439  To regenerate the report, reply with:
   440  #syz regenerate
   441  
   442  You may send multiple commands in a single email message.
   443  `, bugToExtID["WARNING: b second"], bugToExtID["WARNING: b first"]))
   444  
   445  	// Wait the next pair of reminders.
   446  	c.advanceTime(time.Hour * 24 * 31)
   447  	_, err = c.GET("/cron/subsystem_reports")
   448  	c.expectOK(err)
   449  }
   450  
   451  func TestSubsystemRemindersModeration(t *testing.T) {
   452  	c := NewCtx(t)
   453  	defer c.Close()
   454  
   455  	client := c.makeClient(clientSubsystemRemind, keySubsystemRemind, true)
   456  	build := testBuild(1)
   457  	client.UploadBuild(build)
   458  	bugToExtID := map[string]string{}
   459  
   460  	aFirst := testCrash(build, 1)
   461  	aFirst.Title = `WARNING: a first`
   462  	aFirst.GuiltyFiles = []string{"a.c"}
   463  	client.ReportCrash(aFirst)
   464  	bugToExtID[aFirst.Title] = client.pollEmailExtID()
   465  	c.advanceTime(time.Hour)
   466  
   467  	aSecond := testCrash(build, 1)
   468  	aSecond.Title = `WARNING: a second`
   469  	aSecond.GuiltyFiles = []string{"a.c"}
   470  	client.ReportCrash(aSecond)
   471  	bugToExtID[aSecond.Title] = client.pollEmailExtID()
   472  	c.advanceTime(time.Hour)
   473  
   474  	// Report them again.
   475  	c.advanceTime(time.Hour * 24 * 14)
   476  	client.ReportCrash(aFirst)
   477  	client.ReportCrash(aSecond)
   478  
   479  	_, err := c.GET("/cron/subsystem_reports")
   480  	c.expectOK(err)
   481  
   482  	// Expect the reminder for subsystemA.
   483  	replyA := client.pollEmailBug()
   484  	c.expectEQ(replyA.Subject, "[moderation] Monthly subsystemA report (Jan 2000)")
   485  
   486  	// Moderate the subsystemA list.
   487  	c.advanceTime(time.Hour)
   488  	c.incomingEmail(replyA.Sender, "#syz upstream\n")
   489  	// Also emulate the second email that would come from the mailing list.
   490  	// The email should be silently ignored.
   491  	c.incomingEmail(replyA.Sender, "#syz upstream\n",
   492  		EmailOptFrom("moderation@syzkaller.com"), EmailOptOrigFrom("user@user.com"))
   493  
   494  	// Expect the normal report.
   495  	reply := client.pollEmailBug()
   496  	c.expectEQ(reply.Subject, "[syzbot] Monthly subsystemA report (Jan 2000)")
   497  	c.expectEQ(reply.To, []string{"bugs@syzkaller.com", "subsystemA@list.com", "subsystemA@person.com"})
   498  	c.expectEQ(reply.Cc, []string(nil))
   499  	c.expectEQ(reply.Body, fmt.Sprintf(`Hello subsystemA maintainers/developers,
   500  
   501  This is a 30-day syzbot report for the subsystemA subsystem.
   502  All related reports/information can be found at:
   503  https://testapp.appspot.com/subsystem-reminders/s/subsystemA
   504  
   505  During the period, 2 new issues were detected and 0 were fixed.
   506  In total, 2 issues are still open.
   507  
   508  Some of the still happening issues:
   509  
   510  Ref Crashes Repro Title
   511  <1> 2       No    WARNING: a first
   512                    https://testapp.appspot.com/bug?extid=%[1]v
   513  <2> 2       No    WARNING: a second
   514                    https://testapp.appspot.com/bug?extid=%[2]v
   515  
   516  ---
   517  This report is generated by a bot. It may contain errors.
   518  See https://goo.gl/tpsmEJ for more information about syzbot.
   519  syzbot engineers can be reached at syzkaller@googlegroups.com.
   520  
   521  To disable reminders for individual bugs, reply with the following command:
   522  #syz set <Ref> no-reminders
   523  
   524  To change bug's subsystems, reply with:
   525  #syz set <Ref> subsystems: new-subsystem
   526  
   527  You may send multiple commands in a single email message.
   528  `, bugToExtID["WARNING: a first"], bugToExtID["WARNING: a second"]))
   529  }
   530  
   531  func TestSubsystemReportGeneration(t *testing.T) {
   532  	c := NewCtx(t)
   533  	defer c.Close()
   534  
   535  	client := c.makeClient(clientSubsystemRemind, keySubsystemRemind, true)
   536  	build := testBuild(1)
   537  	client.UploadBuild(build)
   538  	bugToExtID := map[string]string{}
   539  
   540  	// This crash will be too old.
   541  	crash := testCrash(build, 1)
   542  	crash.Title = `WARNING: old crash`
   543  	crash.GuiltyFiles = []string{"a.c"}
   544  	client.ReportCrash(crash)
   545  	client.pollEmailBug()
   546  	c.advanceTime(time.Hour * 24 * 40)
   547  
   548  	// Emulate one fixed bug.
   549  	aFixed := testCrash(build, 1)
   550  	aFixed.Title = `WARNING: fixed bug`
   551  	aFixed.GuiltyFiles = []string{"a.c"}
   552  	client.ReportCrash(aFixed)
   553  	bugToExtID[aFixed.Title] = client.pollEmailExtID()
   554  	c.advanceTime(time.Hour)
   555  	updReply, _ := client.ReportingUpdate(&dashapi.BugUpdate{
   556  		ID:         bugToExtID[aFixed.Title],
   557  		Status:     dashapi.BugStatusOpen,
   558  		FixCommits: []string{"foo: fix1"},
   559  	})
   560  	c.expectEQ(updReply.OK, true)
   561  	c.expectOK(client.UploadCommits([]dashapi.Commit{
   562  		{Hash: "hash1", Title: "foo: fix1", Date: timeNow(c.ctx)},
   563  	}))
   564  
   565  	allCrashes := []*dashapi.Crash{}
   566  
   567  	// Report 4 crashes with a reproducer.
   568  	var biggestReproCrash *dashapi.Crash
   569  	for i := 2; i <= 5; i++ {
   570  		crash := testCrash(build, 1)
   571  		crash.Title = fmt.Sprintf(`WARNING: has repro %d`, i+1)
   572  		crash.GuiltyFiles = []string{"a.c"}
   573  		client.ReportCrash(crash)
   574  		bugToExtID[crash.Title] = client.pollEmailExtID()
   575  		c.advanceTime(time.Hour)
   576  
   577  		crash.ReproOpts = []byte("some opts")
   578  		crash.ReproSyz = []byte("getpid()")
   579  		client.ReportCrash(crash)
   580  		client.pollEmailBug()
   581  		c.advanceTime(time.Hour)
   582  
   583  		for j := 3; j <= i; j++ {
   584  			client.ReportCrash(crash)
   585  			c.advanceTime(time.Hour)
   586  		}
   587  		allCrashes = append(allCrashes, crash)
   588  		biggestReproCrash = crash
   589  	}
   590  
   591  	// Report 5 crashes without a reproducer.
   592  	for i := 1; i <= 5; i++ {
   593  		crash := testCrash(build, 1)
   594  		crash.Title = fmt.Sprintf(`WARNING: no repro %d`, i+1)
   595  		crash.GuiltyFiles = []string{"a.c"}
   596  		client.ReportCrash(crash)
   597  		bugToExtID[crash.Title] = client.pollEmailExtID()
   598  		c.advanceTime(time.Hour)
   599  
   600  		for j := 2; j <= i; j++ {
   601  			client.ReportCrash(crash)
   602  			c.advanceTime(time.Hour)
   603  		}
   604  		allCrashes = append(allCrashes, crash)
   605  	}
   606  
   607  	c.advanceTime(time.Hour * 24 * 14)
   608  	for _, crash := range allCrashes {
   609  		client.ReportCrash(crash)
   610  		c.advanceTime(time.Hour)
   611  	}
   612  
   613  	// Now query the report.
   614  	_, err := c.GET("/cron/subsystem_reports")
   615  	c.expectOK(err)
   616  
   617  	reply := client.pollEmailBug()
   618  	c.expectEQ(reply.Subject, "[moderation] Monthly subsystemA report (Feb 2000)")
   619  	c.expectEQ(reply.To, []string{"moderation@syzkaller.com"})
   620  	c.expectEQ(reply.Cc, []string(nil))
   621  	c.expectEQ(reply.Body, fmt.Sprintf(`Hello subsystemA maintainers/developers,
   622  
   623  This is a 30-day syzbot report for the subsystemA subsystem.
   624  All related reports/information can be found at:
   625  https://testapp.appspot.com/subsystem-reminders/s/subsystemA
   626  
   627  During the period, 9 new issues were detected and 1 were fixed.
   628  In total, 10 issues are still open and 1 has been fixed so far.
   629  
   630  Some of the still happening issues:
   631  
   632  Ref Crashes Repro Title
   633  <1> 6       Yes   WARNING: has repro 6
   634                    https://testapp.appspot.com/bug?extid=%[1]v
   635  <2> 6       No    WARNING: no repro 6
   636                    https://testapp.appspot.com/bug?extid=%[2]v
   637  <3> 5       Yes   WARNING: has repro 5
   638                    https://testapp.appspot.com/bug?extid=%[3]v
   639  <4> 5       No    WARNING: no repro 5
   640                    https://testapp.appspot.com/bug?extid=%[4]v
   641  <5> 4       Yes   WARNING: has repro 4
   642                    https://testapp.appspot.com/bug?extid=%[5]v
   643  <6> 3       Yes   WARNING: has repro 3
   644                    https://testapp.appspot.com/bug?extid=%[6]v
   645  
   646  The report will be sent to: [subsystemA@list.com subsystemA@person.com].
   647  
   648  ---
   649  This report is generated by a bot. It may contain errors.
   650  See https://goo.gl/tpsmEJ for more information about syzbot.
   651  syzbot engineers can be reached at syzkaller@googlegroups.com.
   652  
   653  To disable reminders for individual bugs, reply with the following command:
   654  #syz set <Ref> no-reminders
   655  
   656  To change bug's subsystems, reply with:
   657  #syz set <Ref> subsystems: new-subsystem
   658  
   659  If the report looks fine to you, reply with:
   660  #syz upstream
   661  
   662  To regenerate the report, reply with:
   663  #syz regenerate
   664  
   665  You may send multiple commands in a single email message.
   666  `,
   667  		bugToExtID["WARNING: has repro 6"],
   668  		bugToExtID["WARNING: no repro 6"],
   669  		bugToExtID["WARNING: has repro 5"],
   670  		bugToExtID["WARNING: no repro 5"],
   671  		bugToExtID["WARNING: has repro 4"],
   672  		bugToExtID["WARNING: has repro 3"],
   673  	))
   674  
   675  	// Add one more crash and regenerate.
   676  	client.ReportCrash(biggestReproCrash)
   677  	c.advanceTime(time.Hour)
   678  
   679  	c.incomingEmail(reply.Sender, "#syz regenerate\n")
   680  	c.advanceTime(time.Hour)
   681  
   682  	_, err = c.GET("/cron/subsystem_reports")
   683  	c.expectOK(err)
   684  
   685  	secondReply := client.pollEmailBug()
   686  	c.expectEQ(secondReply.Subject, "[moderation] Monthly subsystemA report (Feb 2000)")
   687  	c.expectNE(reply.Sender, secondReply.Sender)
   688  	c.expectTrue(strings.Contains(secondReply.Body, `7       Yes   WARNING: has repro 6`))
   689  }
   690  
   691  func TestSubsystemRemindersNoReport(t *testing.T) {
   692  	c := NewCtx(t)
   693  	defer c.Close()
   694  
   695  	client := c.makeClient(clientSubsystemRemind, keySubsystemRemind, true)
   696  	build := testBuild(1)
   697  	client.UploadBuild(build)
   698  
   699  	cFirst := testCrash(build, 1)
   700  	cFirst.Title = `WARNING: c first`
   701  	cFirst.GuiltyFiles = []string{"c.c"}
   702  	client.ReportCrash(cFirst)
   703  	client.pollEmailBug()
   704  	c.advanceTime(time.Hour)
   705  
   706  	cSecond := testCrash(build, 1)
   707  	cSecond.Title = `WARNING: c second`
   708  	cSecond.GuiltyFiles = []string{"c.c"}
   709  	client.ReportCrash(cSecond)
   710  	client.pollEmailBug()
   711  	c.advanceTime(time.Hour)
   712  
   713  	// Report them again.
   714  	c.advanceTime(time.Hour * 24 * 14)
   715  	client.ReportCrash(cFirst)
   716  	client.ReportCrash(cSecond)
   717  
   718  	_, err := c.GET("/cron/subsystem_reports")
   719  	c.expectOK(err)
   720  
   721  	// Expect no reminders for subsystemC.
   722  	client.expectNoEmail()
   723  }
   724  
   725  // nolint: goconst
   726  func TestNoRemindersWithDiscussions(t *testing.T) {
   727  	c := NewCtx(t)
   728  	defer c.Close()
   729  
   730  	client := c.makeClient(clientSubsystemRemind, keySubsystemRemind, true)
   731  	build := testBuild(1)
   732  	client.UploadBuild(build)
   733  
   734  	bugToExtID := map[string]string{}
   735  
   736  	// WARNING: a first
   737  	aFirst := testCrash(build, 1)
   738  	aFirst.Title = `WARNING: a first`
   739  	aFirst.GuiltyFiles = []string{"a.c"}
   740  	client.ReportCrash(aFirst)
   741  	bugToExtID[aFirst.Title] = client.pollEmailExtID()
   742  	c.advanceTime(time.Hour)
   743  
   744  	// WARNING: a second (1 crash)
   745  	aSecond := testCrash(build, 1)
   746  	aSecond.Title = `WARNING: a second`
   747  	aSecond.GuiltyFiles = []string{"a.c"}
   748  	client.ReportCrash(aSecond)
   749  	bugToExtID[aSecond.Title] = client.pollEmailExtID()
   750  	c.advanceTime(time.Hour)
   751  
   752  	// WARNING: a third (1 crash)
   753  	aThird := testCrash(build, 1)
   754  	aThird.Title = `WARNING: a third`
   755  	aThird.GuiltyFiles = []string{"a.c"}
   756  	client.ReportCrash(aThird)
   757  	bugToExtID[aThird.Title] = client.pollEmailExtID()
   758  	c.advanceTime(time.Hour)
   759  
   760  	// Report bugs once more to pretend they're still valid.
   761  	c.advanceTime(time.Hour * 24 * 10)
   762  	client.ReportCrash(aFirst)
   763  	client.ReportCrash(aSecond)
   764  	client.ReportCrash(aThird)
   765  
   766  	// Add a recent discussion to the second bug.
   767  	c.expectOK(client.SaveDiscussion(&dashapi.SaveDiscussionReq{
   768  		Discussion: &dashapi.Discussion{
   769  			ID:      "123",
   770  			Source:  dashapi.DiscussionLore,
   771  			Type:    dashapi.DiscussionReport,
   772  			Subject: "Some discussion",
   773  			BugIDs:  []string{bugToExtID[aSecond.Title]},
   774  			Messages: []dashapi.DiscussionMessage{
   775  				{
   776  					ID:       "123",
   777  					External: true,
   778  					Time:     timeNow(c.ctx),
   779  				},
   780  			},
   781  		},
   782  	}))
   783  	c.advanceTime(time.Hour)
   784  
   785  	_, err := c.GET("/cron/subsystem_reports")
   786  	c.expectOK(err)
   787  
   788  	reply := client.pollEmailBug()
   789  	// Verify that the second bug is not present.
   790  	c.expectEQ(reply.Body, fmt.Sprintf(`Hello subsystemA maintainers/developers,
   791  
   792  This is a 30-day syzbot report for the subsystemA subsystem.
   793  All related reports/information can be found at:
   794  https://testapp.appspot.com/subsystem-reminders/s/subsystemA
   795  
   796  During the period, 3 new issues were detected and 0 were fixed.
   797  In total, 3 issues are still open.
   798  
   799  Some of the still happening issues:
   800  
   801  Ref Crashes Repro Title
   802  <1> 2       No    WARNING: a first
   803                    https://testapp.appspot.com/bug?extid=%[1]v
   804  <2> 2       No    WARNING: a third
   805                    https://testapp.appspot.com/bug?extid=%[2]v
   806  
   807  The report will be sent to: [subsystemA@list.com subsystemA@person.com].
   808  
   809  ---
   810  This report is generated by a bot. It may contain errors.
   811  See https://goo.gl/tpsmEJ for more information about syzbot.
   812  syzbot engineers can be reached at syzkaller@googlegroups.com.
   813  
   814  To disable reminders for individual bugs, reply with the following command:
   815  #syz set <Ref> no-reminders
   816  
   817  To change bug's subsystems, reply with:
   818  #syz set <Ref> subsystems: new-subsystem
   819  
   820  If the report looks fine to you, reply with:
   821  #syz upstream
   822  
   823  To regenerate the report, reply with:
   824  #syz regenerate
   825  
   826  You may send multiple commands in a single email message.
   827  `, bugToExtID["WARNING: a first"], bugToExtID["WARNING: a third"]))
   828  }
   829  
   830  // nolint: goconst
   831  func TestSkipSubsystemReminders(t *testing.T) {
   832  	c := NewCtx(t)
   833  	defer c.Close()
   834  
   835  	client := c.makeClient(clientSubsystemRemind, keySubsystemRemind, true)
   836  	build := testBuild(1)
   837  	client.UploadBuild(build)
   838  
   839  	bugToExtID := map[string]string{}
   840  
   841  	// WARNING: a first
   842  	aFirst := testCrash(build, 1)
   843  	aFirst.Title = `WARNING: a first`
   844  	aFirst.GuiltyFiles = []string{"a.c"}
   845  	client.ReportCrash(aFirst)
   846  	bugToExtID[aFirst.Title] = client.pollEmailExtID()
   847  	c.advanceTime(time.Hour)
   848  
   849  	// WARNING: a second (1 crash)
   850  	aSecond := testCrash(build, 1)
   851  	aSecond.Title = `WARNING: a second`
   852  	aSecond.GuiltyFiles = []string{"a.c"}
   853  	client.ReportCrash(aSecond)
   854  	bugToExtID[aSecond.Title] = client.pollEmailExtID()
   855  	c.advanceTime(time.Hour)
   856  
   857  	// WARNING: a third (1 crash)
   858  	aThird := testCrash(build, 1)
   859  	aThird.Title = `WARNING: a third`
   860  	aThird.GuiltyFiles = []string{"a.c"}
   861  	client.ReportCrash(aThird)
   862  	bugToExtID[aThird.Title] = client.pollEmailExtID()
   863  	c.advanceTime(time.Hour)
   864  
   865  	// WARNING: a fourth (1 crash)
   866  	aFourth := testCrash(build, 1)
   867  	aFourth.Title = `WARNING: a fourth`
   868  	aFourth.GuiltyFiles = []string{"a.c"}
   869  	client.ReportCrash(aFourth)
   870  	bugToExtID[aFourth.Title] = client.pollEmailExtID()
   871  	c.advanceTime(time.Hour)
   872  
   873  	// Report bugs once more to pretend they're still valid.
   874  	c.advanceTime(time.Hour * 24 * 14)
   875  	client.ReportCrash(aFirst)
   876  	client.ReportCrash(aSecond)
   877  	client.ReportCrash(aThird)
   878  	client.ReportCrash(aFourth)
   879  	c.advanceTime(time.Hour)
   880  
   881  	_, err := c.GET("/cron/subsystem_reports")
   882  	c.expectOK(err)
   883  
   884  	// Expect the reminder for subsystemA.
   885  	reply := client.pollEmailBug()
   886  	c.expectEQ(reply.Subject, "[moderation] Monthly subsystemA report (Jan 2000)")
   887  	c.expectEQ(reply.To, []string{"moderation@syzkaller.com"})
   888  	c.expectEQ(reply.Cc, []string(nil))
   889  	c.expectEQ(reply.Body, fmt.Sprintf(`Hello subsystemA maintainers/developers,
   890  
   891  This is a 30-day syzbot report for the subsystemA subsystem.
   892  All related reports/information can be found at:
   893  https://testapp.appspot.com/subsystem-reminders/s/subsystemA
   894  
   895  During the period, 4 new issues were detected and 0 were fixed.
   896  In total, 4 issues are still open.
   897  
   898  Some of the still happening issues:
   899  
   900  Ref Crashes Repro Title
   901  <1> 2       No    WARNING: a first
   902                    https://testapp.appspot.com/bug?extid=%[1]v
   903  <2> 2       No    WARNING: a fourth
   904                    https://testapp.appspot.com/bug?extid=%[4]v
   905  <3> 2       No    WARNING: a second
   906                    https://testapp.appspot.com/bug?extid=%[2]v
   907  <4> 2       No    WARNING: a third
   908                    https://testapp.appspot.com/bug?extid=%[3]v
   909  
   910  The report will be sent to: [subsystemA@list.com subsystemA@person.com].
   911  
   912  ---
   913  This report is generated by a bot. It may contain errors.
   914  See https://goo.gl/tpsmEJ for more information about syzbot.
   915  syzbot engineers can be reached at syzkaller@googlegroups.com.
   916  
   917  To disable reminders for individual bugs, reply with the following command:
   918  #syz set <Ref> no-reminders
   919  
   920  To change bug's subsystems, reply with:
   921  #syz set <Ref> subsystems: new-subsystem
   922  
   923  If the report looks fine to you, reply with:
   924  #syz upstream
   925  
   926  To regenerate the report, reply with:
   927  #syz regenerate
   928  
   929  You may send multiple commands in a single email message.
   930  `, bugToExtID["WARNING: a first"], bugToExtID["WARNING: a second"],
   931  		bugToExtID["WARNING: a third"], bugToExtID["WARNING: a fourth"]))
   932  
   933  	c.incomingEmail(reply.Sender, `> In total, 4 issues are still open.
   934  >
   935  > Some of the still happening issues:
   936  >
   937  > Ref Crashes Repro Title
   938  > <1> 2       No    WARNING: a first
   939  >                   https://testapp.appspot.com/bug?extid=%[1]v
   940  > <2> 2       No    WARNING: a fourth
   941  >                   https://testapp.appspot.com/bug?extid=%[4]v
   942  #syz set <2> no-reminders
   943  > <3> 2       No    WARNING: a second
   944  >                   https://testapp.appspot.com/bug?extid=%[2]v
   945  > <4> 2       No    WARNING: a third
   946  >                   https://testapp.appspot.com/bug?extid=%[3]v
   947  #syz set <4> no-reminders
   948  `)
   949  
   950  	// Prepare for the next monthly report.
   951  	c.advanceTime(time.Hour * 24 * 31)
   952  	client.ReportCrash(aFirst)
   953  	client.ReportCrash(aSecond)
   954  	client.ReportCrash(aThird)
   955  	client.ReportCrash(aFourth)
   956  	c.advanceTime(time.Hour)
   957  
   958  	_, err = c.GET("/cron/subsystem_reports")
   959  	c.expectOK(err)
   960  
   961  	reply = client.pollEmailBug()
   962  	c.expectEQ(reply.Body, fmt.Sprintf(`Hello subsystemA maintainers/developers,
   963  
   964  This is a 30-day syzbot report for the subsystemA subsystem.
   965  All related reports/information can be found at:
   966  https://testapp.appspot.com/subsystem-reminders/s/subsystemA
   967  
   968  During the period, 0 new issues were detected and 0 were fixed.
   969  In total, 4 issues are still open.
   970  
   971  Some of the still happening issues:
   972  
   973  Ref Crashes Repro Title
   974  <1> 3       No    WARNING: a first
   975                    https://testapp.appspot.com/bug?extid=%[1]v
   976  <2> 3       No    WARNING: a second
   977                    https://testapp.appspot.com/bug?extid=%[2]v
   978  
   979  The report will be sent to: [subsystemA@list.com subsystemA@person.com].
   980  
   981  ---
   982  This report is generated by a bot. It may contain errors.
   983  See https://goo.gl/tpsmEJ for more information about syzbot.
   984  syzbot engineers can be reached at syzkaller@googlegroups.com.
   985  
   986  To disable reminders for individual bugs, reply with the following command:
   987  #syz set <Ref> no-reminders
   988  
   989  To change bug's subsystems, reply with:
   990  #syz set <Ref> subsystems: new-subsystem
   991  
   992  If the report looks fine to you, reply with:
   993  #syz upstream
   994  
   995  To regenerate the report, reply with:
   996  #syz regenerate
   997  
   998  You may send multiple commands in a single email message.
   999  `, bugToExtID["WARNING: a first"], bugToExtID["WARNING: a second"]))
  1000  }
  1001  
  1002  // nolint: goconst
  1003  func TestRemindersPriority(t *testing.T) {
  1004  	c := NewCtx(t)
  1005  	defer c.Close()
  1006  
  1007  	client := c.makeClient(clientSubsystemRemind, keySubsystemRemind, true)
  1008  	cc := EmailOptCC([]string{"bugs@syzkaller.com", "default@maintainers.com"})
  1009  	build := testBuild(1)
  1010  	client.UploadBuild(build)
  1011  
  1012  	// WARNING: a first, low prio, has repro
  1013  	aFirst := testCrash(build, 1)
  1014  	aFirst.Title = `WARNING: a first`
  1015  	aFirst.GuiltyFiles = []string{"a.c"}
  1016  	aFirst.ReproOpts = []byte("some opts")
  1017  	aFirst.ReproSyz = []byte("getpid()")
  1018  	client.ReportCrash(aFirst)
  1019  	sender, firstExtID := client.pollEmailAndExtID()
  1020  	c.incomingEmail(sender, "#syz set prio: low\n",
  1021  		EmailOptFrom("test@requester.com"), cc)
  1022  	c.advanceTime(time.Hour)
  1023  
  1024  	// WARNING: a second, normal prio
  1025  	aSecond := testCrash(build, 1)
  1026  	aSecond.Title = `WARNING: a second`
  1027  	aSecond.GuiltyFiles = []string{"a.c"}
  1028  	client.ReportCrash(aSecond)
  1029  	secondExtID := client.pollEmailExtID()
  1030  	c.advanceTime(time.Hour)
  1031  
  1032  	// WARNING: a third, high prio
  1033  	aThird := testCrash(build, 1)
  1034  	aThird.Title = `WARNING: a third`
  1035  	aThird.GuiltyFiles = []string{"a.c"}
  1036  	client.ReportCrash(aThird)
  1037  	sender, thirdExtID := client.pollEmailAndExtID()
  1038  	c.incomingEmail(sender, "#syz set prio: high\n",
  1039  		EmailOptFrom("test@requester.com"), cc)
  1040  	c.advanceTime(time.Hour)
  1041  
  1042  	// Report bugs once more to pretend they're still valid.
  1043  	c.advanceTime(time.Hour * 24 * 10)
  1044  	client.ReportCrash(aFirst)
  1045  	client.ReportCrash(aSecond)
  1046  	client.ReportCrash(aThird)
  1047  
  1048  	_, err := c.GET("/cron/subsystem_reports")
  1049  	c.expectOK(err)
  1050  
  1051  	reply := client.pollEmailBug()
  1052  	// Verify that the second bug is not present.
  1053  	c.expectEQ(reply.Body, fmt.Sprintf(`Hello subsystemA maintainers/developers,
  1054  
  1055  This is a 30-day syzbot report for the subsystemA subsystem.
  1056  All related reports/information can be found at:
  1057  https://testapp.appspot.com/subsystem-reminders/s/subsystemA
  1058  
  1059  During the period, 2 new issues were detected and 0 were fixed.
  1060  In total, 2 issues are still open.
  1061  There is also 1 low-priority issue.
  1062  
  1063  Some of the still happening issues:
  1064  
  1065  Ref Crashes Repro Title
  1066  <1> 2       No    WARNING: a third
  1067                    https://testapp.appspot.com/bug?extid=%[1]v
  1068  <2> 2       No    WARNING: a second
  1069                    https://testapp.appspot.com/bug?extid=%[2]v
  1070  
  1071  The report will be sent to: [subsystemA@list.com subsystemA@person.com].
  1072  
  1073  ---
  1074  This report is generated by a bot. It may contain errors.
  1075  See https://goo.gl/tpsmEJ for more information about syzbot.
  1076  syzbot engineers can be reached at syzkaller@googlegroups.com.
  1077  
  1078  To disable reminders for individual bugs, reply with the following command:
  1079  #syz set <Ref> no-reminders
  1080  
  1081  To change bug's subsystems, reply with:
  1082  #syz set <Ref> subsystems: new-subsystem
  1083  
  1084  If the report looks fine to you, reply with:
  1085  #syz upstream
  1086  
  1087  To regenerate the report, reply with:
  1088  #syz regenerate
  1089  
  1090  You may send multiple commands in a single email message.
  1091  `, thirdExtID, secondExtID, firstExtID))
  1092  }