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

     1  // Copyright 2019 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  	"context"
     8  	"fmt"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/google/go-cmp/cmp"
    14  	"github.com/google/syzkaller/dashboard/dashapi"
    15  	"github.com/google/syzkaller/pkg/email"
    16  )
    17  
    18  func TestEmailNotifUpstreamEmbargo(t *testing.T) {
    19  	c := NewCtx(t)
    20  	defer c.Close()
    21  
    22  	build := testBuild(1)
    23  	c.client2.UploadBuild(build)
    24  
    25  	crash := testCrash(build, 1)
    26  	c.client2.ReportCrash(crash)
    27  	report := c.pollEmailBug()
    28  	c.expectEQ(report.To, []string{"test@syzkaller.com"})
    29  
    30  	// Upstreaming happens after 14 days, so no emails yet.
    31  	c.advanceTime(13 * 24 * time.Hour)
    32  	c.expectNoEmail()
    33  
    34  	// Now we should get notification about upstreaming and upstream report:
    35  	c.advanceTime(2 * 24 * time.Hour)
    36  	notifUpstream := c.pollEmailBug()
    37  	upstreamReport := c.pollEmailBug()
    38  	c.expectEQ(notifUpstream.Subject, crash.Title)
    39  	c.expectEQ(notifUpstream.Sender, report.Sender)
    40  	c.expectEQ(notifUpstream.Body, "Sending this report to the next reporting stage.")
    41  	c.expectEQ(upstreamReport.Subject, "[syzbot] "+crash.Title)
    42  	c.expectNE(upstreamReport.Sender, report.Sender)
    43  	c.expectEQ(upstreamReport.To, []string{"bugs@syzkaller.com", "default@maintainers.com"})
    44  }
    45  
    46  func TestEmailNotifUpstreamSkip(t *testing.T) {
    47  	c := NewCtx(t)
    48  	defer c.Close()
    49  
    50  	build := testBuild(1)
    51  	c.client2.UploadBuild(build)
    52  
    53  	crash := testCrash(build, 1)
    54  	crash.Title = "skip with repro 1"
    55  	c.client2.ReportCrash(crash)
    56  	report := c.pollEmailBug()
    57  	c.expectEQ(report.To, []string{"test@syzkaller.com"})
    58  
    59  	// No emails yet.
    60  	c.expectNoEmail()
    61  
    62  	// Now upload repro and it should be auto-upstreamed.
    63  	crash.ReproOpts = []byte("repro opts")
    64  	crash.ReproSyz = []byte("getpid()")
    65  	c.client2.ReportCrash(crash)
    66  	notifUpstream := c.pollEmailBug()
    67  	upstreamReport := c.pollEmailBug()
    68  	c.expectEQ(notifUpstream.Sender, report.Sender)
    69  	c.expectEQ(notifUpstream.Body, "Sending this report to the next reporting stage.")
    70  	c.expectNE(upstreamReport.Sender, report.Sender)
    71  	c.expectEQ(upstreamReport.To, []string{"bugs@syzkaller.com", "default@maintainers.com"})
    72  }
    73  
    74  func TestEmailNotifBadFix(t *testing.T) {
    75  	c := NewCtx(t)
    76  	defer c.Close()
    77  
    78  	client := c.publicClient
    79  
    80  	build := testBuild(1)
    81  	client.UploadBuild(build)
    82  
    83  	// Fake more active managers.
    84  	for i := 1; i < 5; i++ {
    85  		client.UploadBuild(testBuild(i + 1))
    86  	}
    87  
    88  	crash := testCrash(build, 1)
    89  	client.ReportCrash(crash)
    90  	report := c.pollEmailBug()
    91  	c.expectEQ(report.To, []string{"test@syzkaller.com"})
    92  	_, extBugID, err := email.RemoveAddrContext(report.Sender)
    93  	c.expectOK(err)
    94  
    95  	c.incomingEmail(report.Sender, "#syz fix some: commit title")
    96  	c.expectNoEmail()
    97  
    98  	// Notification about bad fixing commit should be send after 90 days.
    99  	c.advanceTime(50 * 24 * time.Hour)
   100  	c.expectNoEmail()
   101  	c.advanceTime(35 * 24 * time.Hour)
   102  	c.expectNoEmail()
   103  	c.advanceTime(10 * 24 * time.Hour)
   104  	notif := c.pollEmailBug()
   105  	t.Logf("%s", notif.Body)
   106  
   107  	expectReply := fmt.Sprintf(`This bug is marked as fixed by commit:
   108  some: commit title
   109  
   110  But I can't find it in the tested trees[1] for more than 90 days.
   111  Is it a correct commit? Please update it by replying:
   112  
   113  #syz fix: exact-commit-title
   114  
   115  Until then the bug is still considered open and new crashes with
   116  the same signature are ignored.
   117  
   118  Kernel: access-public-email
   119  Dashboard link: https://testapp.appspot.com/bug?extid=%s
   120  
   121  ---
   122  [1] I expect the commit to be present in:
   123  
   124  1. branch1 branch of
   125  repo1
   126  
   127  2. branch2 branch of
   128  repo2
   129  
   130  3. branch3 branch of
   131  repo3
   132  
   133  4. branch4 branch of
   134  repo4
   135  
   136  The full list of 5 trees can be found at
   137  https://testapp.appspot.com/access-public-email/repos
   138  `, extBugID)
   139  
   140  	if diff := cmp.Diff(expectReply, notif.Body); diff != "" {
   141  		t.Errorf("wrong notification text: %s", diff)
   142  		fmt.Printf("received notification:\n%s\n", notif.Body)
   143  	}
   144  	// No notifications for another 14 days, then another one.
   145  	c.advanceTime(13 * 24 * time.Hour)
   146  	c.expectNoEmail()
   147  	c.advanceTime(2 * 24 * time.Hour)
   148  	notif = c.pollEmailBug()
   149  	if !strings.Contains(notif.Body, "This bug is marked as fixed by commit:\nsome: commit title\n") {
   150  		t.Fatalf("bad notification text: %q", notif.Body)
   151  	}
   152  }
   153  
   154  func TestBugObsoleting(t *testing.T) {
   155  	// To simplify test we specify all dates in days from a fixed point in time.
   156  	const day = 24 * time.Hour
   157  	days := func(n int) time.Time {
   158  		t := time.Date(2000, 0, 0, 0, 0, 0, 0, time.UTC)
   159  		return t.Add(time.Duration(n+1) * day)
   160  	}
   161  	tests := []struct {
   162  		bug    *Bug
   163  		period time.Duration
   164  	}{
   165  		// Final bug with just 1 crash: max final period.
   166  		{
   167  			bug: &Bug{
   168  				FirstTime:  days(0),
   169  				LastTime:   days(0),
   170  				NumCrashes: 1,
   171  				Reporting:  []BugReporting{{Reported: days(0)}},
   172  			},
   173  			period: 100 * day,
   174  		},
   175  		// Non-final bug with just 1 crash: max non-final period.
   176  		{
   177  			bug: &Bug{
   178  				FirstTime:  days(0),
   179  				LastTime:   days(0),
   180  				NumCrashes: 1,
   181  				Reporting:  []BugReporting{{Reported: days(0)}, {}},
   182  			},
   183  			period: 60 * day,
   184  		},
   185  		// Special manger: max period that that manager.
   186  		{
   187  			bug: &Bug{
   188  				FirstTime:  days(0),
   189  				LastTime:   days(0),
   190  				NumCrashes: 1,
   191  				HappenedOn: []string{"special-obsoleting"},
   192  				Reporting:  []BugReporting{{Reported: days(0)}, {}},
   193  			},
   194  			period: 20 * day,
   195  		},
   196  		// Special manger and a non-special: normal rules.
   197  		{
   198  			bug: &Bug{
   199  				FirstTime:  days(0),
   200  				LastTime:   days(0),
   201  				NumCrashes: 1,
   202  				HappenedOn: []string{"special-obsoleting", "non-special-manager"},
   203  				Reporting:  []BugReporting{{Reported: days(0)}},
   204  			},
   205  			period: 100 * day,
   206  		},
   207  		// Happened a lot: min period.
   208  		{
   209  			bug: &Bug{
   210  				FirstTime:  days(0),
   211  				LastTime:   days(1),
   212  				NumCrashes: 1000,
   213  				Reporting:  []BugReporting{{Reported: days(0)}},
   214  			},
   215  			period: 80 * day,
   216  		},
   217  	}
   218  	c := context.Background()
   219  	for i, test := range tests {
   220  		test.bug.Namespace = "test1"
   221  		got := test.bug.obsoletePeriod(c)
   222  		if got != test.period {
   223  			t.Errorf("test #%v: got: %.2f, want %.2f",
   224  				i, float64(got/time.Hour)/24, float64(test.period/time.Hour)/24)
   225  		}
   226  	}
   227  }
   228  
   229  func TestEmailNotifObsoleted(t *testing.T) {
   230  	c := NewCtx(t)
   231  	defer c.Close()
   232  
   233  	build := testBuild(1)
   234  	c.client2.UploadBuild(build)
   235  
   236  	crash := testCrash(build, 1)
   237  	crash.Maintainers = []string{"maintainer@syzkaller.com"}
   238  	c.client2.ReportCrash(crash)
   239  	report := c.pollEmailBug()
   240  	// Need to upstream so that it's not auto-upstreamed before obsoleted.
   241  	c.incomingEmail(report.Sender, "#syz upstream")
   242  	report = c.pollEmailBug()
   243  	// Add more people to bug CC.
   244  	c.incomingEmail(report.Sender, "wow", EmailOptCC([]string{"somebody@else.com"}))
   245  
   246  	// Bug is open, new crashes don't create new bug.
   247  	c.client2.ReportCrash(crash)
   248  	c.expectNoEmail()
   249  
   250  	// Not yet.
   251  	c.advanceTime(59 * 24 * time.Hour)
   252  	c.expectNoEmail()
   253  
   254  	// Now!
   255  	c.advanceTime(2 * 24 * time.Hour)
   256  	notif := c.pollEmailBug()
   257  	if !strings.Contains(notif.Body, "Auto-closing this bug as obsolete") {
   258  		t.Fatalf("bad notification text: %q", notif.Body)
   259  	}
   260  	c.expectEQ(notif.To, []string{"bugs@syzkaller.com", "default@maintainers.com",
   261  		"default@sender.com", "somebody@else.com"})
   262  
   263  	// New crash must create new bug.
   264  	c.client2.ReportCrash(crash)
   265  	report = c.pollEmailBug()
   266  	c.expectEQ(report.Subject, "title1 (2)")
   267  	// Now the same, but for the last reporting (must have smaller CC list).
   268  	c.incomingEmail(report.Sender, "#syz upstream", EmailOptCC([]string{"test@syzkaller.com"}))
   269  	report = c.pollEmailBug()
   270  	c.incomingEmail(report.Sender, "#syz upstream",
   271  		EmailOptCC([]string{"bugs@syzkaller.com", "default@maintainers.com"}))
   272  	report = c.pollEmailBug()
   273  	_ = report
   274  
   275  	c.advanceTime(101 * 24 * time.Hour)
   276  	notif = c.pollEmailBug()
   277  	if !strings.Contains(notif.Body, "Auto-closing this bug as obsolete") {
   278  		t.Fatalf("bad notification text: %q", notif.Body)
   279  	}
   280  	c.expectEQ(notif.Subject, crash.Title+" (2)")
   281  	c.expectEQ(notif.To, []string{"bugs2@syzkaller.com"})
   282  }
   283  
   284  func TestEmailNotifNotObsoleted(t *testing.T) {
   285  	c := NewCtx(t)
   286  	defer c.Close()
   287  
   288  	build := testBuild(1)
   289  	c.client2.UploadBuild(build)
   290  
   291  	// Crashes with repro are not auto-obsoleted.
   292  	crash1 := testCrash(build, 1)
   293  	crash1.ReproSyz = []byte("repro")
   294  	c.client2.ReportCrash(crash1)
   295  	report1 := c.pollEmailBug()
   296  	c.incomingEmail(report1.Sender, "#syz upstream")
   297  	report1 = c.pollEmailBug()
   298  	_ = report1
   299  
   300  	// This crash will get another crash later.
   301  	crash2 := testCrash(build, 2)
   302  	c.client2.ReportCrash(crash2)
   303  	report2 := c.pollEmailBug()
   304  	c.incomingEmail(report2.Sender, "#syz upstream")
   305  	report2 = c.pollEmailBug()
   306  	_ = report2
   307  
   308  	// This crash will get some activity later.
   309  	crash3 := testCrash(build, 3)
   310  	c.client2.ReportCrash(crash3)
   311  	report3 := c.pollEmailBug()
   312  	c.incomingEmail(report3.Sender, "#syz upstream")
   313  	report3 = c.pollEmailBug()
   314  
   315  	// This will be obsoleted (just to check that we have timings right).
   316  	c.advanceTime(24 * time.Hour)
   317  	crash4 := testCrash(build, 4)
   318  	c.client2.ReportCrash(crash4)
   319  	report4 := c.pollEmailBug()
   320  	c.incomingEmail(report4.Sender, "#syz upstream")
   321  	report4 = c.pollEmailBug()
   322  
   323  	c.advanceTime(59 * 24 * time.Hour)
   324  	c.expectNoEmail()
   325  
   326  	c.client2.ReportCrash(crash2)
   327  	c.incomingEmail(report3.Sender, "I am looking at it")
   328  
   329  	c.advanceTime(5 * 24 * time.Hour)
   330  	// Only crash 4 is obsoleted.
   331  	notif := c.pollEmailBug()
   332  	c.expectEQ(notif.Sender, report4.Sender)
   333  	c.expectNoEmail()
   334  
   335  	// Crash 3 also obsoleted after some time.
   336  	c.advanceTime(20 * 24 * time.Hour)
   337  	notif = c.pollEmailBug()
   338  	c.expectEQ(notif.Sender, report3.Sender)
   339  }
   340  
   341  func TestEmailNotifObsoletedManager(t *testing.T) {
   342  	// Crashes with repro are auto-obsoleted if happen on a particular manager only.
   343  	c := NewCtx(t)
   344  	defer c.Close()
   345  
   346  	build := testBuild(1)
   347  	build.Manager = noFixBisectionManager
   348  	c.client2.UploadBuild(build)
   349  	crash := testCrashWithRepro(build, 1)
   350  	c.client2.ReportCrash(crash)
   351  	report := c.pollEmailBug()
   352  	c.incomingEmail(report.Sender, "#syz upstream")
   353  	report = c.pollEmailBug()
   354  	_ = report
   355  	c.advanceTime(200 * 24 * time.Hour)
   356  	notif := c.pollEmailBug()
   357  	c.expectTrue(strings.Contains(notif.Body, "Auto-closing this bug as obsolete"))
   358  }
   359  
   360  func TestExtNotifUpstreamEmbargo(t *testing.T) {
   361  	c := NewCtx(t)
   362  	defer c.Close()
   363  
   364  	build1 := testBuild(1)
   365  	c.client.UploadBuild(build1)
   366  
   367  	crash1 := testCrash(build1, 1)
   368  	c.client.ReportCrash(crash1)
   369  	rep := c.client.pollBug()
   370  
   371  	// Specify fixing commit for the bug.
   372  	reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{
   373  		ID:     rep.ID,
   374  		Status: dashapi.BugStatusOpen,
   375  	})
   376  	c.expectEQ(reply.OK, true)
   377  	c.client.pollNotifs(0)
   378  	c.advanceTime(20 * 24 * time.Hour)
   379  	notif := c.client.pollNotifs(1)[0]
   380  	c.expectEQ(notif.ID, rep.ID)
   381  	c.expectEQ(notif.Type, dashapi.BugNotifUpstream)
   382  }
   383  
   384  func TestExtNotifUpstreamOnHold(t *testing.T) {
   385  	c := NewCtx(t)
   386  	defer c.Close()
   387  
   388  	build1 := testBuild(1)
   389  	c.client.UploadBuild(build1)
   390  
   391  	crash1 := testCrash(build1, 1)
   392  	c.client.ReportCrash(crash1)
   393  	rep := c.client.pollBug()
   394  
   395  	// Specify fixing commit for the bug.
   396  	reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{
   397  		ID:     rep.ID,
   398  		Status: dashapi.BugStatusOpen,
   399  		OnHold: true,
   400  	})
   401  	c.expectEQ(reply.OK, true)
   402  	c.advanceTime(20 * 24 * time.Hour)
   403  	c.client.pollNotifs(0)
   404  }