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 }