github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/dashboard/app/reporting_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 "context" 8 "fmt" 9 "html" 10 "reflect" 11 "regexp" 12 "testing" 13 "time" 14 15 "github.com/google/go-cmp/cmp" 16 "github.com/google/syzkaller/dashboard/dashapi" 17 "github.com/google/syzkaller/pkg/coveragedb" 18 "github.com/google/syzkaller/pkg/coveragedb/mocks" 19 "github.com/google/syzkaller/pkg/email" 20 "github.com/google/syzkaller/sys/targets" 21 "github.com/stretchr/testify/assert" 22 "github.com/stretchr/testify/mock" 23 ) 24 25 func TestReportBug(t *testing.T) { 26 c := NewCtx(t) 27 defer c.Close() 28 29 build := testBuild(1) 30 c.client.UploadBuild(build) 31 32 crash1 := &dashapi.Crash{ 33 BuildID: "build1", 34 Title: "title1", 35 Maintainers: []string{`"Foo Bar" <foo@bar.com>`, `bar@foo.com`}, 36 Log: []byte("log1"), 37 Flags: dashapi.CrashUnderStrace, 38 Report: []byte("report1"), 39 MachineInfo: []byte("machine info 1"), 40 GuiltyFiles: []string{"a.c"}, 41 } 42 c.client.ReportCrash(crash1) 43 44 // Must get no reports for "unknown" type. 45 resp, _ := c.client.ReportingPollBugs("unknown") 46 c.expectEQ(len(resp.Reports), 0) 47 48 // Must get a proper report for "test" type. 49 resp, _ = c.client.ReportingPollBugs("test") 50 c.expectEQ(len(resp.Reports), 1) 51 rep := resp.Reports[0] 52 c.expectNE(rep.ID, "") 53 _, dbCrash, dbBuild := c.loadBug(rep.ID) 54 want := &dashapi.BugReport{ 55 Type: dashapi.ReportNew, 56 BugStatus: dashapi.BugStatusOpen, 57 Namespace: "test1", 58 Config: []byte(`{"Index":1}`), 59 ID: rep.ID, 60 OS: targets.Linux, 61 Arch: targets.AMD64, 62 VMArch: targets.AMD64, 63 UserSpaceArch: targets.AMD64, 64 First: true, 65 Moderation: true, 66 Title: "title1", 67 Link: fmt.Sprintf("https://testapp.appspot.com/bug?extid=%v", rep.ID), 68 CreditEmail: fmt.Sprintf("syzbot+%v@testapp.appspotmail.com", rep.ID), 69 Maintainers: []string{"bar@foo.com", "foo@bar.com", "subsystemA@list.com", "subsystemA@person.com"}, 70 CompilerID: "compiler1", 71 BuildID: "build1", 72 BuildTime: timeNow(c.ctx), 73 KernelRepo: "repo1", 74 KernelRepoAlias: "repo1 branch1", 75 KernelBranch: "branch1", 76 KernelCommit: "1111111111111111111111111111111111111111", 77 KernelCommitTitle: build.KernelCommitTitle, 78 KernelCommitDate: buildCommitDate, 79 KernelConfig: []byte("config1"), 80 KernelConfigLink: externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig), 81 SyzkallerCommit: "syzkaller_commit1", 82 MachineInfo: []byte("machine info 1"), 83 MachineInfoLink: externalLink(c.ctx, textMachineInfo, dbCrash.MachineInfo), 84 Log: []byte("log1"), 85 LogLink: externalLink(c.ctx, textCrashLog, dbCrash.Log), 86 LogHasStrace: true, 87 Report: []byte("report1"), 88 ReportLink: externalLink(c.ctx, textCrashReport, dbCrash.Report), 89 ReproOpts: []uint8{}, 90 CrashID: rep.CrashID, 91 CrashTime: timeNow(c.ctx), 92 NumCrashes: 1, 93 Manager: "manager1", 94 HappenedOn: []string{"repo1 branch1"}, 95 Assets: []dashapi.Asset{}, 96 ReportElements: &dashapi.ReportElements{GuiltyFiles: []string{"a.c"}}, 97 Subsystems: []dashapi.BugSubsystem{ 98 { 99 Name: "subsystemA", 100 Link: "https://testapp.appspot.com/test1/s/subsystemA", 101 }, 102 }, 103 } 104 c.expectEQ(want, rep) 105 106 // Since we did not update bug status yet, should get the same report again. 107 c.expectEQ(c.client.pollBug(), want) 108 109 // Now add syz repro and check that we get another bug report. 110 crash1.ReproOpts = []byte("some opts") 111 crash1.ReproSyz = []byte("getpid()") 112 want.Type = dashapi.ReportRepro 113 want.First = false 114 want.ReproSyz = []byte(syzReproPrefix + "#some opts\ngetpid()") 115 want.ReproOpts = []byte("some opts") 116 c.client.ReportCrash(crash1) 117 rep1 := c.client.pollBug() 118 c.expectNE(want.CrashID, rep1.CrashID) 119 _, dbCrash, _ = c.loadBug(rep.ID) 120 want.CrashID = rep1.CrashID 121 want.NumCrashes = 2 122 want.ReproSyzLink = externalLink(c.ctx, textReproSyz, dbCrash.ReproSyz) 123 want.LogLink = externalLink(c.ctx, textCrashLog, dbCrash.Log) 124 want.ReportLink = externalLink(c.ctx, textCrashReport, dbCrash.Report) 125 c.expectEQ(want, rep1) 126 127 reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ 128 ID: rep.ID, 129 Status: dashapi.BugStatusOpen, 130 ReproLevel: dashapi.ReproLevelSyz, 131 }) 132 c.expectEQ(reply.OK, true) 133 134 // After bug update should not get the report again. 135 c.client.pollBugs(0) 136 137 // Now close the bug in the first reporting. 138 c.client.updateBug(rep.ID, dashapi.BugStatusUpstream, "") 139 140 // Check that bug updates for the first reporting fail now. 141 reply, _ = c.client.ReportingUpdate(&dashapi.BugUpdate{ID: rep.ID, Status: dashapi.BugStatusOpen}) 142 c.expectEQ(reply.OK, false) 143 144 // Report another crash with syz repro for this bug, 145 // ensure that we report the new crash in the next reporting. 146 crash1.Report = []byte("report2") 147 c.client.ReportCrash(crash1) 148 149 // Check that we get the report in the second reporting. 150 rep2 := c.client.pollBug() 151 c.expectNE(rep2.ID, "") 152 c.expectNE(rep2.ID, rep.ID) 153 want.Type = dashapi.ReportNew 154 want.ID = rep2.ID 155 want.Report = []byte("report2") 156 want.LogLink = rep2.LogLink 157 want.ReportLink = rep2.ReportLink 158 want.CrashID = rep2.CrashID 159 want.ReproSyzLink = rep2.ReproSyzLink 160 want.ReproOpts = []byte("some opts") 161 want.Link = fmt.Sprintf("https://testapp.appspot.com/bug?extid=%v", rep2.ID) 162 want.CreditEmail = fmt.Sprintf("syzbot+%v@testapp.appspotmail.com", rep2.ID) 163 want.First = true 164 want.Moderation = false 165 want.Config = []byte(`{"Index":2}`) 166 want.NumCrashes = 3 167 c.expectEQ(want, rep2) 168 169 // Check that that we can't upstream the bug in the final reporting. 170 reply, _ = c.client.ReportingUpdate(&dashapi.BugUpdate{ 171 ID: rep2.ID, 172 Status: dashapi.BugStatusUpstream, 173 }) 174 c.expectEQ(reply.OK, false) 175 } 176 177 func TestInvalidBug(t *testing.T) { 178 c := NewCtx(t) 179 defer c.Close() 180 181 build := testBuild(1) 182 c.client.UploadBuild(build) 183 184 crash1 := testCrashWithRepro(build, 1) 185 c.client.ReportCrash(crash1) 186 187 rep := c.client.pollBug() 188 c.expectEQ(rep.Title, "title1") 189 190 reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ 191 ID: rep.ID, 192 Status: dashapi.BugStatusOpen, 193 ReproLevel: dashapi.ReproLevelC, 194 }) 195 c.expectEQ(reply.OK, true) 196 197 { 198 closed, _ := c.client.ReportingPollClosed([]string{rep.ID, "foobar"}) 199 c.expectEQ(len(closed), 0) 200 } 201 202 // Mark the bug as invalid. 203 c.client.updateBug(rep.ID, dashapi.BugStatusInvalid, "") 204 205 { 206 closed, _ := c.client.ReportingPollClosed([]string{rep.ID, "foobar"}) 207 c.expectEQ(len(closed), 1) 208 c.expectEQ(closed[0], rep.ID) 209 } 210 211 // Now it should not be reported in either reporting. 212 c.client.pollBugs(0) 213 214 // Now a similar crash happens again. 215 crash2 := &dashapi.Crash{ 216 BuildID: "build1", 217 Title: "title1", 218 Log: []byte("log2"), 219 Report: []byte("report2"), 220 ReproC: []byte("int main() { return 1; }"), 221 } 222 c.client.ReportCrash(crash2) 223 224 // Now it should be reported again. 225 rep = c.client.pollBug() 226 c.expectNE(rep.ID, "") 227 _, dbCrash, dbBuild := c.loadBug(rep.ID) 228 want := &dashapi.BugReport{ 229 Type: dashapi.ReportNew, 230 BugStatus: dashapi.BugStatusOpen, 231 Namespace: "test1", 232 Config: []byte(`{"Index":1}`), 233 ID: rep.ID, 234 OS: targets.Linux, 235 Arch: targets.AMD64, 236 VMArch: targets.AMD64, 237 UserSpaceArch: targets.AMD64, 238 First: true, 239 Moderation: true, 240 Title: "title1 (2)", 241 Link: fmt.Sprintf("https://testapp.appspot.com/bug?extid=%v", rep.ID), 242 CreditEmail: fmt.Sprintf("syzbot+%v@testapp.appspotmail.com", rep.ID), 243 BuildID: "build1", 244 BuildTime: timeNow(c.ctx), 245 CompilerID: "compiler1", 246 KernelRepo: "repo1", 247 KernelRepoAlias: "repo1 branch1", 248 KernelBranch: "branch1", 249 KernelCommit: "1111111111111111111111111111111111111111", 250 KernelCommitTitle: build.KernelCommitTitle, 251 KernelCommitDate: buildCommitDate, 252 KernelConfig: []byte("config1"), 253 KernelConfigLink: externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig), 254 SyzkallerCommit: "syzkaller_commit1", 255 Log: []byte("log2"), 256 LogLink: externalLink(c.ctx, textCrashLog, dbCrash.Log), 257 Report: []byte("report2"), 258 ReportLink: externalLink(c.ctx, textCrashReport, dbCrash.Report), 259 ReproC: []byte("int main() { return 1; }"), 260 ReproCLink: externalLink(c.ctx, textReproC, dbCrash.ReproC), 261 ReproOpts: []uint8{}, 262 CrashID: rep.CrashID, 263 CrashTime: timeNow(c.ctx), 264 NumCrashes: 1, 265 Manager: "manager1", 266 HappenedOn: []string{"repo1 branch1"}, 267 Assets: []dashapi.Asset{}, 268 ReportElements: &dashapi.ReportElements{}, 269 } 270 c.expectEQ(want, rep) 271 c.client.ReportFailedRepro(testCrashID(crash1)) 272 } 273 274 func TestReportingQuota(t *testing.T) { 275 c := NewCtx(t) 276 defer c.Close() 277 278 c.updateReporting("test1", "reporting1", 279 func(r Reporting) Reporting { 280 // Set a low daily limit. 281 r.DailyLimit = 2 282 return r 283 }) 284 285 build := testBuild(1) 286 c.client.UploadBuild(build) 287 288 const numReports = 5 289 for i := 0; i < numReports; i++ { 290 c.client.ReportCrash(testCrash(build, i)) 291 } 292 293 for _, reports := range []int{2, 2, 1, 0, 0} { 294 c.advanceTime(24 * time.Hour) 295 c.client.pollBugs(reports) 296 // Out of quota for today, so must get 0 reports. 297 c.client.pollBugs(0) 298 } 299 } 300 301 func TestReproReportingQuota(t *testing.T) { 302 // Test that new repro reports are also covered by daily limits. 303 c := NewCtx(t) 304 defer c.Close() 305 306 client := c.client 307 c.updateReporting("test1", "reporting1", 308 func(r Reporting) Reporting { 309 // Set a low daily limit. 310 r.DailyLimit = 2 311 return r 312 }) 313 314 build := testBuild(1) 315 client.UploadBuild(build) 316 317 // First report of two. 318 c.advanceTime(time.Minute) 319 client.ReportCrash(testCrash(build, 1)) 320 client.pollBug() 321 322 // Second report of two. 323 c.advanceTime(time.Minute) 324 crash := testCrash(build, 2) 325 client.ReportCrash(crash) 326 client.pollBug() 327 328 // Now we "find" a reproducer. 329 c.advanceTime(time.Minute) 330 client.ReportCrash(testCrashWithRepro(build, 1)) 331 332 // But there's no quota for it. 333 client.pollBugs(0) 334 335 // Wait a day and the quota appears. 336 c.advanceTime(time.Hour * 24) 337 client.pollBug() 338 } 339 340 // Basic dup scenario: mark one bug as dup of another. 341 func TestReportingDup(t *testing.T) { 342 c := NewCtx(t) 343 defer c.Close() 344 345 build := testBuild(1) 346 c.client.UploadBuild(build) 347 348 crash1 := testCrash(build, 1) 349 c.client.ReportCrash(crash1) 350 351 crash2 := testCrash(build, 2) 352 c.client.ReportCrash(crash2) 353 354 reports := c.client.pollBugs(2) 355 rep1 := reports[0] 356 rep2 := reports[1] 357 358 // Dup. 359 c.client.updateBug(rep2.ID, dashapi.BugStatusDup, rep1.ID) 360 { 361 // Both must be reported as open. 362 closed, _ := c.client.ReportingPollClosed([]string{rep1.ID, rep2.ID}) 363 c.expectEQ(len(closed), 0) 364 } 365 366 // Undup. 367 c.client.updateBug(rep2.ID, dashapi.BugStatusOpen, "") 368 369 // Dup again. 370 c.client.updateBug(rep2.ID, dashapi.BugStatusDup, rep1.ID) 371 372 // Dup crash happens again, new bug must not be created. 373 c.client.ReportCrash(crash2) 374 c.client.pollBugs(0) 375 376 // Now close the original bug, and check that new bugs for dup are now created. 377 c.client.updateBug(rep1.ID, dashapi.BugStatusInvalid, "") 378 { 379 // Now both must be reported as closed. 380 closed, _ := c.client.ReportingPollClosed([]string{rep1.ID, rep2.ID}) 381 c.expectEQ(len(closed), 2) 382 c.expectEQ(closed[0], rep1.ID) 383 c.expectEQ(closed[1], rep2.ID) 384 } 385 386 c.client.ReportCrash(crash2) 387 rep3 := c.client.pollBug() 388 c.expectEQ(rep3.Title, crash2.Title+" (2)") 389 390 // Unduping after the canonical bugs was closed must not work 391 // (we already created new bug for this report). 392 reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ 393 ID: rep2.ID, 394 Status: dashapi.BugStatusOpen, 395 }) 396 c.expectEQ(reply.OK, false) 397 } 398 399 // Dup bug onto a closed bug. 400 // A new crash report must create a new bug. 401 func TestReportingDupToClosed(t *testing.T) { 402 c := NewCtx(t) 403 defer c.Close() 404 405 build := testBuild(1) 406 c.client.UploadBuild(build) 407 408 crash1 := testCrash(build, 1) 409 c.client.ReportCrash(crash1) 410 411 crash2 := testCrash(build, 2) 412 c.client.ReportCrash(crash2) 413 414 reports := c.client.pollBugs(2) 415 c.client.updateBug(reports[0].ID, dashapi.BugStatusInvalid, "") 416 c.client.updateBug(reports[1].ID, dashapi.BugStatusDup, reports[0].ID) 417 418 c.client.ReportCrash(crash2) 419 rep2 := c.client.pollBug() 420 c.expectEQ(rep2.Title, crash2.Title+" (2)") 421 } 422 423 // Test that marking dups across reporting levels is not permitted. 424 func TestReportingDupCrossReporting(t *testing.T) { 425 c := NewCtx(t) 426 defer c.Close() 427 428 build := testBuild(1) 429 c.client.UploadBuild(build) 430 431 crash1 := testCrash(build, 1) 432 c.client.ReportCrash(crash1) 433 434 crash2 := testCrash(build, 2) 435 c.client.ReportCrash(crash2) 436 437 reports := c.client.pollBugs(2) 438 rep1 := reports[0] 439 rep2 := reports[1] 440 441 // Upstream second bug. 442 c.client.updateBug(rep2.ID, dashapi.BugStatusUpstream, "") 443 rep3 := c.client.pollBug() 444 445 { 446 closed, _ := c.client.ReportingPollClosed([]string{rep1.ID, rep2.ID, rep3.ID}) 447 c.expectEQ(len(closed), 1) 448 c.expectEQ(closed[0], rep2.ID) 449 } 450 451 // Duping must fail all ways. 452 cmds := []*dashapi.BugUpdate{ 453 {ID: rep1.ID, DupOf: rep1.ID}, 454 {ID: rep1.ID, DupOf: rep2.ID}, 455 {ID: rep2.ID, DupOf: rep1.ID}, 456 {ID: rep2.ID, DupOf: rep2.ID}, 457 {ID: rep2.ID, DupOf: rep3.ID}, 458 {ID: rep3.ID, DupOf: rep1.ID}, 459 {ID: rep3.ID, DupOf: rep2.ID}, 460 {ID: rep3.ID, DupOf: rep3.ID}, 461 } 462 for _, cmd := range cmds { 463 t.Logf("duping %v -> %v", cmd.ID, cmd.DupOf) 464 cmd.Status = dashapi.BugStatusDup 465 reply, _ := c.client.ReportingUpdate(cmd) 466 c.expectEQ(reply.OK, false) 467 } 468 // Special case of cross-reporting duping: 469 cmd := &dashapi.BugUpdate{ 470 Status: dashapi.BugStatusDup, 471 ID: rep1.ID, 472 DupOf: rep3.ID, 473 } 474 t.Logf("duping %v -> %v", cmd.ID, cmd.DupOf) 475 reply, _ := c.client.ReportingUpdate(cmd) 476 c.expectTrue(reply.OK) 477 } 478 479 // Test that dups can't form a cycle. 480 // The test builds cycles of length 1..4. 481 func TestReportingDupCycle(t *testing.T) { 482 c := NewCtx(t) 483 defer c.Close() 484 485 build := testBuild(1) 486 c.client.UploadBuild(build) 487 488 const N = 4 489 reps := make([]*dashapi.BugReport, N) 490 for i := 0; i < N; i++ { 491 t.Logf("*************** %v ***************", i) 492 c.client.ReportCrash(testCrash(build, i)) 493 reps[i] = c.client.pollBug() 494 replyError := "Can't dup bug to itself." 495 if i != 0 { 496 replyError = "Setting this dup would lead to a bug cycle, cycles are not allowed." 497 reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ 498 Status: dashapi.BugStatusDup, 499 ID: reps[i-1].ID, 500 DupOf: reps[i].ID, 501 }) 502 c.expectEQ(reply.OK, true) 503 } 504 reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ 505 Status: dashapi.BugStatusDup, 506 ID: reps[i].ID, 507 DupOf: reps[0].ID, 508 }) 509 c.expectEQ(reply.OK, false) 510 c.expectEQ(reply.Error, false) 511 c.expectEQ(reply.Text, replyError) 512 c.advanceTime(24 * time.Hour) 513 } 514 } 515 516 func TestReportingFilter(t *testing.T) { 517 c := NewCtx(t) 518 defer c.Close() 519 520 build := testBuild(1) 521 c.client.UploadBuild(build) 522 523 crash1 := testCrash(build, 1) 524 crash1.Title = "skip with repro 1" 525 c.client.ReportCrash(crash1) 526 527 // This does not skip first reporting, because it does not have repro. 528 rep1 := c.client.pollBug() 529 c.expectEQ(string(rep1.Config), `{"Index":1}`) 530 531 crash1.ReproSyz = []byte("getpid()") 532 c.client.ReportCrash(crash1) 533 534 // This has repro but was already reported to first reporting, 535 // so repro must go to the first reporting as well. 536 rep2 := c.client.pollBug() 537 c.expectEQ(string(rep2.Config), `{"Index":1}`) 538 539 // Now upstream it and it must go to the second reporting. 540 c.client.updateBug(rep1.ID, dashapi.BugStatusUpstream, "") 541 542 rep3 := c.client.pollBug() 543 c.expectEQ(string(rep3.Config), `{"Index":2}`) 544 545 // Now report a bug that must go to the second reporting right away. 546 crash2 := testCrash(build, 2) 547 crash2.Title = "skip with repro 2" 548 crash2.ReproSyz = []byte("getpid()") 549 c.client.ReportCrash(crash2) 550 551 rep4 := c.client.pollBug() 552 c.expectEQ(string(rep4.Config), `{"Index":2}`) 553 } 554 555 func TestMachineInfo(t *testing.T) { 556 c := NewCtx(t) 557 defer c.Close() 558 559 build := testBuild(1) 560 c.client.UploadBuild(build) 561 562 machineInfo := []byte("info1") 563 564 // Create a crash with machine information and check the returned machine 565 // information field is equal. 566 crash := &dashapi.Crash{ 567 BuildID: "build1", 568 Title: "title1", 569 Maintainers: []string{`"Foo Bar" <foo@bar.com>`, `bar@foo.com`}, 570 Log: []byte("log1"), 571 Report: []byte("report1"), 572 MachineInfo: machineInfo, 573 } 574 c.client.ReportCrash(crash) 575 rep := c.client.pollBug() 576 c.expectEQ(machineInfo, rep.MachineInfo) 577 578 // Check that a link to machine information page is created on the dashboard, 579 // and the content is correct. 580 indexPage, err := c.AuthGET(AccessAdmin, "/test1") 581 c.expectOK(err) 582 bugLinkRegex := regexp.MustCompile(`<a href="(/bug\?extid=[^"]+)">title1</a>`) 583 bugLinkSubmatch := bugLinkRegex.FindSubmatch(indexPage) 584 c.expectEQ(len(bugLinkSubmatch), 2) 585 bugURL := html.UnescapeString(string(bugLinkSubmatch[1])) 586 587 bugPage, err := c.AuthGET(AccessAdmin, bugURL) 588 c.expectOK(err) 589 infoLinkRegex := regexp.MustCompile(`<a href="(/text\?tag=MachineInfo[^"]+)">info</a>`) 590 infoLinkSubmatch := infoLinkRegex.FindSubmatch(bugPage) 591 c.expectEQ(len(infoLinkSubmatch), 2) 592 infoURL := html.UnescapeString(string(infoLinkSubmatch[1])) 593 594 receivedInfo, err := c.AuthGET(AccessAdmin, infoURL) 595 c.expectOK(err) 596 c.expectEQ(receivedInfo, machineInfo) 597 } 598 599 func TestAltTitles1(t *testing.T) { 600 c := NewCtx(t) 601 defer c.Close() 602 603 build := testBuild(1) 604 c.client.UploadBuild(build) 605 606 // crash2.AltTitles matches crash1.Title. 607 crash1 := testCrash(build, 1) 608 crash2 := testCrashWithRepro(build, 2) 609 crash2.AltTitles = []string{crash1.Title} 610 611 c.client.ReportCrash(crash1) 612 rep := c.client.pollBug() 613 c.expectEQ(rep.Title, crash1.Title) 614 c.expectEQ(rep.Log, crash1.Log) 615 616 c.client.ReportCrash(crash2) 617 rep = c.client.pollBug() 618 c.expectEQ(rep.Title, crash1.Title) 619 c.expectEQ(rep.Log, crash2.Log) 620 } 621 622 func TestAltTitles2(t *testing.T) { 623 c := NewCtx(t) 624 defer c.Close() 625 626 build := testBuild(1) 627 c.client.UploadBuild(build) 628 629 // crash2.Title matches crash1.AltTitles, but reported in opposite order. 630 crash1 := testCrash(build, 1) 631 crash2 := testCrash(build, 2) 632 crash2.AltTitles = []string{crash1.Title} 633 634 c.client.ReportCrash(crash2) 635 rep := c.client.pollBug() 636 c.expectEQ(rep.Title, crash2.Title) 637 c.expectEQ(rep.Log, crash2.Log) 638 639 c.client.ReportCrash(crash1) 640 c.client.pollBugs(0) 641 } 642 643 func TestAltTitles3(t *testing.T) { 644 c := NewCtx(t) 645 defer c.Close() 646 647 build := testBuild(1) 648 c.client.UploadBuild(build) 649 650 // crash2.AltTitles matches crash1.AltTitles. 651 crash1 := testCrash(build, 1) 652 crash1.AltTitles = []string{"foobar"} 653 crash2 := testCrash(build, 2) 654 crash2.AltTitles = crash1.AltTitles 655 656 c.client.ReportCrash(crash1) 657 c.client.pollBugs(1) 658 c.client.ReportCrash(crash2) 659 c.client.pollBugs(0) 660 } 661 662 func TestAltTitles4(t *testing.T) { 663 c := NewCtx(t) 664 defer c.Close() 665 666 build := testBuild(1) 667 c.client.UploadBuild(build) 668 669 // crash1.AltTitles matches crash2.AltTitles which matches crash3.AltTitles. 670 crash1 := testCrash(build, 1) 671 crash1.AltTitles = []string{"foobar1"} 672 crash2 := testCrash(build, 2) 673 crash2.AltTitles = []string{"foobar1", "foobar2"} 674 crash3 := testCrash(build, 3) 675 crash3.AltTitles = []string{"foobar2"} 676 677 c.client.ReportCrash(crash1) 678 c.client.pollBugs(1) 679 c.client.ReportCrash(crash2) 680 c.client.pollBugs(0) 681 c.client.ReportCrash(crash3) 682 c.client.pollBugs(0) 683 } 684 685 func TestAltTitles5(t *testing.T) { 686 c := NewCtx(t) 687 defer c.Close() 688 689 build := testBuild(1) 690 c.client.UploadBuild(build) 691 692 // Test which of the possible existing bugs we choose for merging. 693 crash1 := testCrash(build, 1) 694 crash1.AltTitles = []string{"foo"} 695 c.client.ReportCrash(crash1) 696 c.client.pollBugs(1) 697 698 crash2 := testCrash(build, 2) 699 crash2.Title = "bar" 700 c.client.ReportCrash(crash2) 701 c.client.pollBugs(1) 702 703 crash3 := testCrash(build, 3) 704 c.client.ReportCrash(crash3) 705 c.client.pollBugs(1) 706 crash3.AltTitles = []string{"bar"} 707 c.client.ReportCrash(crash3) 708 c.client.pollBugs(0) 709 710 crash := testCrashWithRepro(build, 10) 711 crash.Title = "foo" 712 crash.AltTitles = []string{"bar"} 713 c.client.ReportCrash(crash) 714 rep := c.client.pollBug() 715 c.expectEQ(rep.Title, crash2.Title) 716 c.expectEQ(rep.Log, crash.Log) 717 } 718 719 func TestAltTitles6(t *testing.T) { 720 c := NewCtx(t) 721 defer c.Close() 722 723 build := testBuild(1) 724 c.client.UploadBuild(build) 725 726 // Test which of the possible existing bugs we choose for merging in presence of closed bugs. 727 crash1 := testCrash(build, 1) 728 crash1.AltTitles = []string{"foo"} 729 c.client.ReportCrash(crash1) 730 rep := c.client.pollBug() 731 c.client.updateBug(rep.ID, dashapi.BugStatusInvalid, "") 732 c.client.ReportCrash(crash1) 733 c.client.pollBug() 734 735 crash2 := testCrash(build, 2) 736 crash2.Title = "bar" 737 c.client.ReportCrash(crash2) 738 rep = c.client.pollBug() 739 c.client.updateBug(rep.ID, dashapi.BugStatusInvalid, "") 740 741 c.advanceTime(24 * time.Hour) 742 crash3 := testCrash(build, 3) 743 c.client.ReportCrash(crash3) 744 c.client.pollBugs(1) 745 crash3.AltTitles = []string{"foo"} 746 c.client.ReportCrash(crash3) 747 c.client.pollBugs(0) 748 749 crash := testCrashWithRepro(build, 10) 750 crash.Title = "foo" 751 crash.AltTitles = []string{"bar"} 752 c.client.ReportCrash(crash) 753 rep = c.client.pollBug() 754 c.expectEQ(rep.Title, crash1.Title+" (2)") 755 c.expectEQ(rep.Log, crash.Log) 756 } 757 758 func TestAltTitles7(t *testing.T) { 759 c := NewCtx(t) 760 defer c.Close() 761 762 build := testBuild(1) 763 c.client.UploadBuild(build) 764 765 // Test that bug merging is stable: if we started merging into a bug, we continue merging into that bug 766 // even if a better candidate appears. 767 crash1 := testCrash(build, 1) 768 crash1.AltTitles = []string{"foo"} 769 c.client.ReportCrash(crash1) 770 c.client.pollBug() 771 772 // This will be merged into crash1. 773 crash2 := testCrash(build, 2) 774 crash2.AltTitles = []string{"foo"} 775 c.client.ReportCrash(crash2) 776 c.client.pollBugs(0) 777 778 // Now report a better candidate. 779 crash3 := testCrash(build, 3) 780 crash3.Title = "aaa" 781 c.client.ReportCrash(crash3) 782 c.client.pollBug() 783 crash3.AltTitles = []string{crash2.Title} 784 c.client.ReportCrash(crash3) 785 c.client.pollBugs(0) 786 787 // Now report crash2 with a repro and ensure that it's still merged into crash1. 788 crash2.ReproOpts = []byte("some opts") 789 crash2.ReproSyz = []byte("getpid()") 790 c.client.ReportCrash(crash2) 791 rep := c.client.pollBug() 792 c.expectEQ(rep.Title, crash1.Title) 793 c.expectEQ(rep.Log, crash2.Log) 794 } 795 796 func TestDetachExternalTracker(t *testing.T) { 797 c := NewCtx(t) 798 defer c.Close() 799 800 build := testBuild(1) 801 c.client.UploadBuild(build) 802 803 crash1 := testCrash(build, 1) 804 c.client.ReportCrash(crash1) 805 806 // Get single report for "test" type. 807 resp, _ := c.client.ReportingPollBugs("test") 808 c.expectEQ(len(resp.Reports), 1) 809 rep1 := resp.Reports[0] 810 c.expectNE(rep1.ID, "") 811 c.expectEQ(string(rep1.Config), `{"Index":1}`) 812 813 // Signal detach_reporting for current bug. 814 reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ 815 ID: rep1.ID, 816 Status: dashapi.BugStatusUpstream, 817 ReproLevel: dashapi.ReproLevelNone, 818 Link: "http://URI/1", 819 CrashID: rep1.CrashID, 820 }) 821 c.expectEQ(reply.OK, true) 822 823 // Now add syz repro to check it doesn't use first reporting. 824 crash1.ReproOpts = []byte("some opts") 825 crash1.ReproSyz = []byte("getpid()") 826 c.client.ReportCrash(crash1) 827 828 // Fetch bug and check reporting path (Config) is different. 829 rep2 := c.client.pollBug() 830 c.expectNE(rep2.ID, "") 831 c.expectEQ(string(rep2.Config), `{"Index":2}`) 832 833 closed, _ := c.client.ReportingPollClosed([]string{rep1.ID, rep2.ID}) 834 c.expectEQ(len(closed), 1) 835 c.expectEQ(closed[0], rep1.ID) 836 } 837 838 func TestUpdateBugReporting(t *testing.T) { 839 c := NewCtx(t) 840 defer c.Close() 841 setIDs := func(bug *Bug, arr []BugReporting) { 842 for i := range arr { 843 arr[i].ID = bugReportingHash(bug.keyHash(c.ctx), arr[i].Name) 844 } 845 } 846 now := timeNow(c.ctx) 847 // We test against the test2 namespace. 848 cfg := c.config().Namespaces["test2"] 849 tests := []struct { 850 Before []BugReporting 851 After []BugReporting 852 Error bool 853 }{ 854 // Initially empty object. 855 { 856 Before: []BugReporting{}, 857 After: []BugReporting{ 858 { 859 Name: "reporting1", 860 }, 861 { 862 Name: "reporting2", 863 }, 864 { 865 Name: "reporting3", 866 }, 867 }, 868 }, 869 // Prepending and appending new reporting objects, the bug is not reported yet. 870 { 871 Before: []BugReporting{ 872 { 873 Name: "reporting2", 874 }, 875 }, 876 After: []BugReporting{ 877 { 878 Name: "reporting1", 879 }, 880 { 881 Name: "reporting2", 882 }, 883 { 884 Name: "reporting3", 885 }, 886 }, 887 }, 888 // The order or reportings is changed. 889 { 890 Before: []BugReporting{ 891 { 892 Name: "reporting2", 893 }, 894 { 895 Name: "reporting1", 896 }, 897 { 898 Name: "reporting3", 899 }, 900 }, 901 After: []BugReporting{}, 902 Error: true, 903 }, 904 // Prepending and appending new reporting objects, the bug is already reported. 905 { 906 Before: []BugReporting{ 907 { 908 Name: "reporting2", 909 Reported: now, 910 ExtID: "abcd", 911 }, 912 }, 913 After: []BugReporting{ 914 { 915 Name: "reporting1", 916 Closed: now, 917 Reported: now, 918 Dummy: true, 919 }, 920 { 921 Name: "reporting2", 922 Reported: now, 923 ExtID: "abcd", 924 }, 925 { 926 Name: "reporting3", 927 }, 928 }, 929 }, 930 // It must look like as if the new Reporting was immediate. 931 { 932 Before: []BugReporting{ 933 { 934 Name: "reporting1", 935 Reported: now.Add(-24 * time.Hour), 936 ExtID: "abcd", 937 }, 938 { 939 Name: "reporting3", 940 Reported: now, 941 ExtID: "efgh", 942 }, 943 }, 944 After: []BugReporting{ 945 { 946 Name: "reporting1", 947 Reported: now.Add(-24 * time.Hour), 948 ExtID: "abcd", 949 }, 950 { 951 Name: "reporting2", 952 Reported: now.Add(-24 * time.Hour), 953 Closed: now.Add(-24 * time.Hour), 954 Dummy: true, 955 }, 956 { 957 Name: "reporting3", 958 Reported: now, 959 ExtID: "efgh", 960 }, 961 }, 962 }, 963 } 964 for _, test := range tests { 965 bug := &Bug{ 966 Title: "bug", 967 Reporting: test.Before, 968 Namespace: "test2", 969 } 970 setIDs(bug, bug.Reporting) 971 setIDs(bug, test.After) 972 hasError := bug.updateReportings(c.ctx, cfg, now) != nil 973 if hasError != test.Error { 974 t.Errorf("before: %#v, expected error: %v, got error: %v", test.Before, test.Error, hasError) 975 } 976 if !test.Error && !reflect.DeepEqual(bug.Reporting, test.After) { 977 t.Errorf("before: %#v, expected after: %#v, got after: %#v", test.Before, test.After, bug.Reporting) 978 } 979 } 980 } 981 982 func TestFullBugInfo(t *testing.T) { 983 c := NewCtx(t) 984 defer c.Close() 985 986 build := testBuild(1) 987 c.client.UploadBuild(build) 988 989 const crashTitle = "WARNING: abcd" 990 991 // Oldest crash: with strace. 992 crashStrace := testCrashWithRepro(build, 1) 993 crashStrace.Title = crashTitle 994 crashStrace.Flags = dashapi.CrashUnderStrace 995 crashStrace.Report = []byte("with strace") 996 c.client.ReportCrash(crashStrace) 997 rep := c.client.pollBug() 998 999 // Newer: just with repro. 1000 c.advanceTime(24 * 7 * time.Hour) 1001 crashRepro := testCrashWithRepro(build, 1) 1002 crashRepro.Title = crashTitle 1003 crashRepro.Report = []byte("with repro") 1004 c.client.ReportCrash(crashRepro) 1005 1006 // Ensure we have some bisect jobs done. 1007 pollResp := c.client.pollJobs(build.Manager) 1008 c.expectNE(pollResp.ID, "") 1009 jobID := pollResp.ID 1010 done := &dashapi.JobDoneReq{ 1011 ID: jobID, 1012 Build: *testBuild(3), 1013 Log: []byte("bisect log"), 1014 Commits: []dashapi.Commit{ 1015 { 1016 Hash: "111111111111111111111111", 1017 Title: "kernel: break build", 1018 Author: "hacker@kernel.org", 1019 CC: []string{"reviewer1@kernel.org"}, 1020 Date: time.Date(2000, 2, 9, 4, 5, 6, 7, time.UTC), 1021 }, 1022 }, 1023 } 1024 c.client.expectOK(c.client.JobDone(done)) 1025 c.client.pollBug() 1026 1027 // Yet newer: no repro. 1028 c.advanceTime(24 * 7 * time.Hour) 1029 crashNew := testCrash(build, 1) 1030 crashNew.Title = crashTitle 1031 c.client.ReportCrash(crashNew) 1032 1033 // And yet newer. 1034 c.advanceTime(24 * time.Hour) 1035 crashNew2 := testCrash(build, 1) 1036 crashNew2.Title = crashTitle 1037 crashNew2.Report = []byte("newest") 1038 c.client.ReportCrash(crashNew2) 1039 1040 // Also create a bug in another namespace. 1041 otherBuild := testBuild(2) 1042 c.client2.UploadBuild(otherBuild) 1043 1044 otherCrash := testCrash(otherBuild, 1) 1045 otherCrash.Title = crashTitle 1046 otherCrash.ReproOpts = []byte("repro opts") 1047 otherCrash.ReproSyz = []byte("repro syz") 1048 c.client2.ReportCrash(otherCrash) 1049 otherPollMsg := c.client2.pollEmailBug() 1050 1051 _, err := c.POST("/_ah/mail/", fmt.Sprintf(`Sender: syzkaller@googlegroups.com 1052 Date: Tue, 15 Aug 2017 14:59:00 -0700 1053 Message-ID: <1234> 1054 Subject: crash1 1055 From: %v 1056 To: foo@bar.com 1057 Content-Type: text/plain 1058 1059 This email is only needed to capture the link 1060 -- 1061 To post to this group, send email to syzkaller@googlegroups.com. 1062 To view this discussion on the web visit https://groups.google.com/d/msgid/syzkaller/1234@google.com. 1063 For more options, visit https://groups.google.com/d/optout. 1064 `, otherPollMsg.Sender)) 1065 c.expectOK(err) 1066 1067 _, otherExtBugID, _ := email.RemoveAddrContext(otherPollMsg.Sender) 1068 1069 // Query the full bug info. 1070 info, err := c.client.LoadFullBug(&dashapi.LoadFullBugReq{BugID: rep.ID}) 1071 c.expectOK(err) 1072 if info.BisectCause == nil { 1073 t.Fatalf("info.BisectCause is empty") 1074 } 1075 if info.BisectCause.BisectCause == nil { 1076 t.Fatalf("info.BisectCause.BisectCause is empty") 1077 } 1078 c.expectEQ(info.SimilarBugs, []*dashapi.SimilarBugInfo{{ 1079 Title: crashTitle, 1080 Namespace: "test2", 1081 Status: dashapi.BugStatusOpen, 1082 ReproLevel: dashapi.ReproLevelSyz, 1083 Link: "https://testapp.appspot.com/bug?extid=" + otherExtBugID, 1084 ReportLink: "https://groups.google.com/d/msgid/syzkaller/1234@google.com", 1085 }}) 1086 1087 // There must be 3 crashes. 1088 reportsOrder := [][]byte{[]byte("newest"), []byte("with repro"), []byte("with strace")} 1089 c.expectEQ(len(info.Crashes), len(reportsOrder)) 1090 for i, report := range reportsOrder { 1091 c.expectEQ(info.Crashes[i].Report, report) 1092 } 1093 } 1094 1095 func TestUpdateReportApi(t *testing.T) { 1096 c := NewCtx(t) 1097 defer c.Close() 1098 1099 build := testBuild(1) 1100 c.client.UploadBuild(build) 1101 1102 // Report a crash. 1103 c.client.ReportCrash(testCrashWithRepro(build, 1)) 1104 c.client.pollBug() 1105 1106 listResp, err := c.client.BugList() 1107 c.expectOK(err) 1108 c.expectEQ(len(listResp.List), 1) 1109 1110 // Load the bug info. 1111 bugID := listResp.List[0] 1112 rep, err := c.client.LoadBug(bugID) 1113 c.expectOK(err) 1114 1115 // Now update the crash. 1116 setGuiltyFiles := []string{"fs/a.c", "net/b.c"} 1117 err = c.client.UpdateReport(&dashapi.UpdateReportReq{ 1118 BugID: bugID, 1119 CrashID: rep.CrashID, 1120 GuiltyFiles: &setGuiltyFiles, 1121 }) 1122 c.expectOK(err) 1123 1124 // And make sure it's been updated. 1125 ret, err := c.client.LoadBug(bugID) 1126 if err != nil { 1127 t.Fatal(err) 1128 } 1129 if ret.ReportElements == nil { 1130 t.Fatalf("ReportElements is nil") 1131 } 1132 if diff := cmp.Diff(ret.ReportElements.GuiltyFiles, setGuiltyFiles); diff != "" { 1133 t.Fatal(diff) 1134 } 1135 } 1136 1137 func TestReportDecommissionedBugs(t *testing.T) { 1138 c := NewCtx(t) 1139 defer c.Close() 1140 1141 client := c.makeClient(clientTestDecomm, keyTestDecomm, true) 1142 build := testBuild(1) 1143 client.UploadBuild(build) 1144 1145 crash := testCrash(build, 1) 1146 client.ReportCrash(crash) 1147 rep := client.pollBug() 1148 1149 closed, _ := client.ReportingPollClosed([]string{rep.ID}) 1150 c.expectEQ(len(closed), 0) 1151 1152 // And now let's decommission the namespace. 1153 c.decommission(rep.Namespace) 1154 1155 closed, _ = client.ReportingPollClosed([]string{rep.ID}) 1156 c.expectEQ(len(closed), 1) 1157 c.expectEQ(closed[0], rep.ID) 1158 } 1159 1160 func TestObsoletePeriod(t *testing.T) { 1161 base := time.Now() 1162 c := context.Background() 1163 config := getConfig(c) 1164 tests := []struct { 1165 name string 1166 bug *Bug 1167 period time.Duration 1168 }{ 1169 { 1170 name: "frequent final bug", 1171 bug: &Bug{ 1172 // Once in a day. 1173 NumCrashes: 30, 1174 FirstTime: base, 1175 LastTime: base.Add(time.Hour * 24 * 30), 1176 Reporting: []BugReporting{{Reported: base}}, 1177 }, 1178 // 80 days are definitely enough. 1179 period: config.Obsoleting.MinPeriod, 1180 }, 1181 { 1182 name: "very short-living final bug", 1183 bug: &Bug{ 1184 NumCrashes: 5, 1185 FirstTime: base, 1186 LastTime: base.Add(time.Hour * 24), 1187 Reporting: []BugReporting{{Reported: base}}, 1188 }, 1189 // Too few crashes, wait max time. 1190 period: config.Obsoleting.MaxPeriod, 1191 }, 1192 { 1193 name: "rare stable final bug", 1194 bug: &Bug{ 1195 // Once in 20 days. 1196 NumCrashes: 20, 1197 FirstTime: base, 1198 LastTime: base.Add(time.Hour * 24 * 400), 1199 Reporting: []BugReporting{{Reported: base}}, 1200 }, 1201 // Wait max time. 1202 period: config.Obsoleting.MaxPeriod, 1203 }, 1204 { 1205 name: "frequent non-final bug", 1206 bug: &Bug{ 1207 // Once in a day. 1208 NumCrashes: 10, 1209 FirstTime: base, 1210 LastTime: base.Add(time.Hour * 24 * 10), 1211 Reporting: []BugReporting{{}}, 1212 }, 1213 // 40 days are also enough. 1214 period: config.Obsoleting.NonFinalMinPeriod, 1215 }, 1216 } 1217 1218 for _, test := range tests { 1219 t.Run(test.name, func(t *testing.T) { 1220 ret := test.bug.obsoletePeriod(c) 1221 assert.Equal(t, test.period, ret) 1222 }) 1223 } 1224 } 1225 1226 func TestReportRevokedRepro(t *testing.T) { 1227 // There was a bug (#4412) where syzbot infinitely re-reported reproducers 1228 // for a bug that was upstreamed after its repro was revoked. 1229 // Recreate this situation. 1230 c := NewCtx(t) 1231 defer c.Close() 1232 1233 client := c.makeClient(clientPublic, keyPublic, true) 1234 build := testBuild(1) 1235 build.KernelRepo = "git://mygit.com/git.git" 1236 build.KernelBranch = "main" 1237 client.UploadBuild(build) 1238 1239 crash := testCrash(build, 1) 1240 crash.ReproOpts = []byte("repro opts") 1241 crash.ReproSyz = []byte("repro syz") 1242 client.ReportCrash(crash) 1243 rep1 := client.pollBug() 1244 client.expectNE(rep1.ReproSyz, nil) 1245 1246 // Revoke the reproducer. 1247 c.advanceTime(c.config().Obsoleting.ReproRetestStart + time.Hour) 1248 jobResp := client.pollSpecificJobs(build.Manager, dashapi.ManagerJobs{TestPatches: true}) 1249 c.expectEQ(jobResp.Type, dashapi.JobTestPatch) 1250 client.expectOK(client.JobDone(&dashapi.JobDoneReq{ 1251 ID: jobResp.ID, 1252 })) 1253 1254 c.advanceTime(time.Hour) 1255 client.ReportCrash(testCrash(build, 1)) 1256 1257 // Upstream the bug. 1258 c.advanceTime(time.Hour) 1259 client.updateBug(rep1.ID, dashapi.BugStatusUpstream, "") 1260 rep2 := client.pollBug() 1261 1262 // Also ensure that we do not report the revoked reproducer. 1263 client.expectEQ(rep2.Type, dashapi.ReportNew) 1264 client.expectEQ(rep2.ReproSyz, []byte(nil)) 1265 1266 // Expect no further reports. 1267 client.pollBugs(0) 1268 } 1269 1270 func TestWaitForRepro(t *testing.T) { 1271 c := NewCtx(t) 1272 defer c.Close() 1273 1274 client := c.client 1275 c.setWaitForRepro("test1", time.Hour*24) 1276 1277 build := testBuild(1) 1278 client.UploadBuild(build) 1279 1280 // Normal crash witout repro. 1281 client.ReportCrash(testCrash(build, 1)) 1282 client.pollBugs(0) 1283 c.advanceTime(time.Hour * 24) 1284 client.pollBug() 1285 1286 // A crash first without repro, then with it. 1287 client.ReportCrash(testCrash(build, 2)) 1288 c.advanceTime(time.Hour * 12) 1289 client.pollBugs(0) 1290 client.ReportCrash(testCrashWithRepro(build, 2)) 1291 client.pollBug() 1292 1293 // A crash with a reproducer. 1294 c.advanceTime(time.Minute) 1295 client.ReportCrash(testCrashWithRepro(build, 3)) 1296 client.pollBug() 1297 1298 // A crahs that will never have a reproducer. 1299 c.advanceTime(time.Minute) 1300 crash := testCrash(build, 4) 1301 crash.Title = "upstream test error: abcd" 1302 client.ReportCrash(crash) 1303 client.pollBug() 1304 } 1305 1306 // The test mimics the failure described in #5829. 1307 func TestReportRevokedBisectCrash(t *testing.T) { 1308 c := NewCtx(t) 1309 defer c.Close() 1310 1311 client := c.makeClient(clientPublic, keyPublic, true) 1312 build := testBuild(1) 1313 build.KernelRepo = "git://git.com/git.git" 1314 client.UploadBuild(build) 1315 1316 const crashTitle = "WARNING: abcd" 1317 1318 crashRepro := testCrashWithRepro(build, 1) 1319 crashRepro.Title = crashTitle 1320 client.ReportCrash(crashRepro) 1321 1322 // Do a bisection. 1323 pollResp := client.pollJobs(build.Manager) 1324 c.expectNE(pollResp.ID, "") 1325 c.expectEQ(pollResp.Type, dashapi.JobBisectCause) 1326 done := &dashapi.JobDoneReq{ 1327 ID: pollResp.ID, 1328 Build: *testBuild(2), 1329 Log: []byte("bisect log"), 1330 Commits: []dashapi.Commit{ 1331 { 1332 Hash: "111111111111111111111111", 1333 Title: "kernel: break build", 1334 Author: "hacker@kernel.org", 1335 Date: time.Date(2000, 2, 9, 4, 5, 6, 7, time.UTC), 1336 }, 1337 }, 1338 } 1339 client.expectOK(client.JobDone(done)) 1340 report := client.pollBug() 1341 1342 // Revoke the reproducer. 1343 c.advanceTime(c.config().Obsoleting.ReproRetestStart + time.Hour) 1344 resp := client.pollSpecificJobs(build.Manager, dashapi.ManagerJobs{ 1345 TestPatches: true, 1346 }) 1347 c.expectEQ(resp.Type, dashapi.JobTestPatch) 1348 client.expectOK(client.JobDone(&dashapi.JobDoneReq{ 1349 ID: resp.ID, 1350 })) 1351 1352 // Move to the next reporting stage. 1353 c.advanceTime(time.Hour) 1354 client.updateBug(report.ID, dashapi.BugStatusUpstream, "") 1355 report = client.pollBug() 1356 client.expectNE(report.ReproCLink, "") 1357 client.expectEQ(report.ReproIsRevoked, true) 1358 1359 // And now report a new reproducer. 1360 c.advanceTime(time.Hour) 1361 build2 := testBuild(1) 1362 client.UploadBuild(build2) 1363 crashRepro2 := testCrashWithRepro(build2, 2) 1364 crashRepro2.Title = crashTitle 1365 client.ReportCrash(crashRepro2) 1366 1367 // There should be no new report. 1368 // We already reported that the bug has a reproducer. 1369 client.pollBugs(0) 1370 } 1371 1372 func TestCoverageRegression(t *testing.T) { 1373 c := NewCtx(t) 1374 defer c.Close() 1375 1376 mTran1 := mocks.NewReadOnlyTransaction(t) 1377 mTran1.On("Query", mock.Anything, mock.Anything). 1378 Return(newRowIteratorMock(t, []*coveragedb.FileCoverageWithDetails{ 1379 { 1380 Filepath: "file_name.c", 1381 Instrumented: 100, 1382 Covered: 100, 1383 }, 1384 })).Once() 1385 1386 mTran2 := mocks.NewReadOnlyTransaction(t) 1387 mTran2.On("Query", mock.Anything, mock.Anything). 1388 Return(newRowIteratorMock(t, []*coveragedb.FileCoverageWithDetails{ 1389 { 1390 Filepath: "file_name.c", 1391 Instrumented: 0, 1392 }, 1393 })).Once() 1394 1395 m := mocks.NewSpannerClient(t) 1396 m.On("Single"). 1397 Return(mTran1).Once() 1398 m.On("Single"). 1399 Return(mTran2).Once() 1400 1401 c.transformContext = func(ctx context.Context) context.Context { 1402 return setCoverageDBClient(ctx, m) 1403 } 1404 _, err := c.AuthGET(AccessAdmin, "/cron/email_coverage_reports") 1405 assert.NoError(t, err) 1406 assert.Equal(t, 1, len(c.emailSink)) 1407 msg := <-c.emailSink 1408 assert.Equal(t, []string{"test@test.test"}, msg.To) 1409 assert.Equal(t, "coverage-tests coverage regression in December 1999", msg.Subject) 1410 wantLink := "https://testapp.appspot.com/coverage-tests/coverage?" + 1411 "dateto=1999-12-31&min-cover-lines-drop=1&order-by-cover-lines-drop=1&period=month&period_count=2" 1412 assert.Equal(t, `Regressions happened in 'coverage-tests' from November 1999 (30 days) to December 1999 (31 days). 1413 Web version: `+wantLink+` 1414 1415 Blocks diff, Path 1416 -100 /file_name.c 1417 1418 `, msg.Body) 1419 }