github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/dashboard/app/fix_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 "testing" 8 "time" 9 10 "github.com/google/syzkaller/dashboard/dashapi" 11 ) 12 13 // Basic scenario of marking a bug as fixed by a particular commit, 14 // discovering this commit on builder and marking the bug as ultimately fixed. 15 func TestFixBasic(t *testing.T) { 16 c := NewCtx(t) 17 defer c.Close() 18 19 build1 := testBuild(1) 20 c.client.UploadBuild(build1) 21 22 crash1 := testCrash(build1, 1) 23 c.client.ReportCrash(crash1) 24 25 builderPollResp, _ := c.client.BuilderPoll(build1.Manager) 26 c.expectEQ(len(builderPollResp.PendingCommits), 0) 27 28 needRepro, _ := c.client.NeedRepro(testCrashID(crash1)) 29 c.expectEQ(needRepro, true) 30 31 rep := c.client.pollBug() 32 33 // Specify fixing commit for the bug. 34 reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ 35 ID: rep.ID, 36 Status: dashapi.BugStatusOpen, 37 FixCommits: []string{"foo: fix the crash"}, 38 }) 39 c.expectEQ(reply.OK, true) 40 41 // Don't need repro once there are fixing commits. 42 needRepro, _ = c.client.NeedRepro(testCrashID(crash1)) 43 c.expectEQ(needRepro, false) 44 45 // Check that the commit is now passed to builders. 46 builderPollResp, _ = c.client.BuilderPoll(build1.Manager) 47 c.expectEQ(len(builderPollResp.PendingCommits), 1) 48 c.expectEQ(builderPollResp.PendingCommits[0], "foo: fix the crash") 49 50 // Patches must not be reset on other actions. 51 c.client.updateBug(rep.ID, dashapi.BugStatusOpen, "") 52 53 // Upstream commands must fail if patches are already present. 54 // Right course of action is unclear in this situation, 55 // so this test merely documents the current behavior. 56 reply, _ = c.client.ReportingUpdate(&dashapi.BugUpdate{ 57 ID: rep.ID, 58 Status: dashapi.BugStatusUpstream, 59 }) 60 c.expectEQ(reply.OK, false) 61 62 c.client.ReportCrash(crash1) 63 c.client.pollBugs(0) 64 65 // Upload another build with the commit present. 66 build2 := testBuild(2) 67 build2.Manager = build1.Manager 68 build2.Commits = []string{"foo: fix the crash"} 69 c.client.UploadBuild(build2) 70 71 // Check that the commit is now not passed to this builder. 72 builderPollResp, _ = c.client.BuilderPoll(build1.Manager) 73 c.expectEQ(len(builderPollResp.PendingCommits), 0) 74 75 // Ensure that a new crash creates a new bug (the old one must be marked as fixed). 76 c.client.ReportCrash(crash1) 77 rep2 := c.client.pollBug() 78 c.expectEQ(rep2.Title, "title1 (2)") 79 80 // Regression test: previously upstreamming failed because the new bug had fixing commits. 81 c.client.ReportCrash(crash1) 82 c.client.updateBug(rep2.ID, dashapi.BugStatusUpstream, "") 83 c.client.pollBug() 84 } 85 86 // Test bug that is fixed by 2 commits. 87 func TestFixedByTwoCommits(t *testing.T) { 88 c := NewCtx(t) 89 defer c.Close() 90 91 build1 := testBuild(1) 92 c.client.UploadBuild(build1) 93 94 crash1 := testCrash(build1, 1) 95 c.client.ReportCrash(crash1) 96 97 builderPollResp, _ := c.client.BuilderPoll(build1.Manager) 98 c.expectEQ(len(builderPollResp.PendingCommits), 0) 99 100 rep := c.client.pollBug() 101 102 // Specify fixing commit for the bug. 103 reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ 104 ID: rep.ID, 105 Status: dashapi.BugStatusOpen, 106 FixCommits: []string{"bar: prepare for fixing", "\"foo: fix the crash\""}, 107 }) 108 c.expectEQ(reply.OK, true) 109 110 // Check that the commit is now passed to builders. 111 builderPollResp, _ = c.client.BuilderPoll(build1.Manager) 112 c.expectEQ(len(builderPollResp.PendingCommits), 2) 113 c.expectEQ(builderPollResp.PendingCommits[0], "bar: prepare for fixing") 114 c.expectEQ(builderPollResp.PendingCommits[1], "foo: fix the crash") 115 116 // Upload another build with only one of the commits. 117 build2 := testBuild(2) 118 build2.Manager = build1.Manager 119 build2.Commits = []string{"bar: prepare for fixing"} 120 c.client.UploadBuild(build2) 121 122 // Check that it has not fixed the bug. 123 builderPollResp, _ = c.client.BuilderPoll(build1.Manager) 124 c.expectEQ(len(builderPollResp.PendingCommits), 2) 125 c.expectEQ(builderPollResp.PendingCommits[0], "bar: prepare for fixing") 126 c.expectEQ(builderPollResp.PendingCommits[1], "foo: fix the crash") 127 128 c.client.ReportCrash(crash1) 129 c.client.pollBugs(0) 130 131 // Now upload build with both commits. 132 build3 := testBuild(3) 133 build3.Manager = build1.Manager 134 build3.Commits = []string{"foo: fix the crash", "bar: prepare for fixing"} 135 c.client.UploadBuild(build3) 136 137 // Check that the commit is now not passed to this builder. 138 builderPollResp, _ = c.client.BuilderPoll(build1.Manager) 139 c.expectEQ(len(builderPollResp.PendingCommits), 0) 140 141 // Ensure that a new crash creates a new bug (the old one must be marked as fixed). 142 c.client.ReportCrash(crash1) 143 rep2 := c.client.pollBug() 144 c.expectEQ(rep2.Title, "title1 (2)") 145 } 146 147 // A bug is marked as fixed by one commit and then remarked as fixed by another. 148 func TestReFixed(t *testing.T) { 149 c := NewCtx(t) 150 defer c.Close() 151 152 build1 := testBuild(1) 153 c.client.UploadBuild(build1) 154 155 crash1 := testCrash(build1, 1) 156 c.client.ReportCrash(crash1) 157 158 builderPollResp, _ := c.client.BuilderPoll(build1.Manager) 159 c.expectEQ(len(builderPollResp.PendingCommits), 0) 160 161 c.advanceTime(time.Hour) 162 rep := c.client.pollBug() 163 164 bug, _, _ := c.loadBug(rep.ID) 165 c.expectEQ(bug.LastActivity, c.mockedTime) 166 c.expectEQ(bug.FixTime, time.Time{}) 167 168 // Specify fixing commit for the bug. 169 c.advanceTime(time.Hour) 170 reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ 171 ID: rep.ID, 172 Status: dashapi.BugStatusOpen, 173 FixCommits: []string{"a wrong one"}, 174 }) 175 c.expectEQ(reply.OK, true) 176 177 bug, _, _ = c.loadBug(rep.ID) 178 c.expectEQ(bug.LastActivity, c.mockedTime) 179 c.expectEQ(bug.FixTime, c.mockedTime) 180 181 c.advanceTime(time.Hour) 182 reply, _ = c.client.ReportingUpdate(&dashapi.BugUpdate{ 183 ID: rep.ID, 184 Status: dashapi.BugStatusOpen, 185 FixCommits: []string{"the right one"}, 186 }) 187 c.expectEQ(reply.OK, true) 188 189 bug, _, _ = c.loadBug(rep.ID) 190 c.expectEQ(bug.LastActivity, c.mockedTime) 191 c.expectEQ(bug.FixTime, c.mockedTime) 192 193 // No updates, just check that LastActivity time is updated, FixTime preserved. 194 fixTime := c.mockedTime 195 c.advanceTime(time.Hour) 196 reply, _ = c.client.ReportingUpdate(&dashapi.BugUpdate{ 197 ID: rep.ID, 198 Status: dashapi.BugStatusOpen, 199 }) 200 c.expectEQ(reply.OK, true) 201 bug, _, _ = c.loadBug(rep.ID) 202 c.expectEQ(bug.LastActivity, c.mockedTime) 203 c.expectEQ(bug.FixTime, fixTime) 204 205 // Send the same fixing commit, check that LastActivity time is updated, FixTime preserved. 206 c.advanceTime(time.Hour) 207 reply, _ = c.client.ReportingUpdate(&dashapi.BugUpdate{ 208 ID: rep.ID, 209 Status: dashapi.BugStatusOpen, 210 FixCommits: []string{"the right one"}, 211 }) 212 c.expectEQ(reply.OK, true) 213 bug, _, _ = c.loadBug(rep.ID) 214 c.expectEQ(bug.LastActivity, c.mockedTime) 215 c.expectEQ(bug.FixTime, fixTime) 216 217 builderPollResp, _ = c.client.BuilderPoll(build1.Manager) 218 c.expectEQ(len(builderPollResp.PendingCommits), 1) 219 c.expectEQ(builderPollResp.PendingCommits[0], "the right one") 220 221 // Upload another build with the wrong commit. 222 build2 := testBuild(2) 223 build2.Manager = build1.Manager 224 build2.Commits = []string{"a wrong one"} 225 c.client.UploadBuild(build2) 226 227 // Check that it has not fixed the bug. 228 builderPollResp, _ = c.client.BuilderPoll(build1.Manager) 229 c.expectEQ(len(builderPollResp.PendingCommits), 1) 230 c.expectEQ(builderPollResp.PendingCommits[0], "the right one") 231 232 c.client.ReportCrash(crash1) 233 c.client.pollBugs(0) 234 235 // Now upload build with the right commit. 236 build3 := testBuild(3) 237 build3.Manager = build1.Manager 238 build3.Commits = []string{"the right one"} 239 c.client.UploadBuild(build3) 240 241 // Check that the commit is now not passed to this builder. 242 builderPollResp, _ = c.client.BuilderPoll(build1.Manager) 243 c.expectEQ(len(builderPollResp.PendingCommits), 0) 244 } 245 246 // Fixing commit is present on one manager, but missing on another. 247 func TestFixTwoManagers(t *testing.T) { 248 c := NewCtx(t) 249 defer c.Close() 250 251 build1 := testBuild(1) 252 c.client.UploadBuild(build1) 253 254 crash1 := testCrash(build1, 1) 255 c.client.ReportCrash(crash1) 256 257 builderPollResp, _ := c.client.BuilderPoll(build1.Manager) 258 c.expectEQ(len(builderPollResp.PendingCommits), 0) 259 260 rep := c.client.pollBug() 261 262 // Specify fixing commit for the bug. 263 reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ 264 ID: rep.ID, 265 Status: dashapi.BugStatusOpen, 266 FixCommits: []string{"foo: fix the crash"}, 267 }) 268 c.expectEQ(reply.OK, true) 269 270 // Now the second manager appears. 271 build2 := testBuild(2) 272 c.client.UploadBuild(build2) 273 274 // Check that the commit is now passed to builders. 275 builderPollResp, _ = c.client.BuilderPoll(build1.Manager) 276 c.expectEQ(len(builderPollResp.PendingCommits), 1) 277 c.expectEQ(builderPollResp.PendingCommits[0], "foo: fix the crash") 278 279 builderPollResp, _ = c.client.BuilderPoll(build2.Manager) 280 c.expectEQ(len(builderPollResp.PendingCommits), 1) 281 c.expectEQ(builderPollResp.PendingCommits[0], "foo: fix the crash") 282 283 // Now first manager picks up the commit. 284 build3 := testBuild(3) 285 build3.Manager = build1.Manager 286 build3.Commits = []string{"foo: fix the crash"} 287 c.client.UploadBuild(build3) 288 289 // Check that the commit is now not passed to this builder. 290 builderPollResp, _ = c.client.BuilderPoll(build1.Manager) 291 c.expectEQ(len(builderPollResp.PendingCommits), 0) 292 293 // But still passed to another. 294 builderPollResp, _ = c.client.BuilderPoll(build2.Manager) 295 c.expectEQ(len(builderPollResp.PendingCommits), 1) 296 c.expectEQ(builderPollResp.PendingCommits[0], "foo: fix the crash") 297 298 // Check that the bug is still open. 299 c.client.ReportCrash(crash1) 300 c.client.pollBugs(0) 301 302 // Now the second manager picks up the commit. 303 build4 := testBuild(4) 304 build4.Manager = build2.Manager 305 build4.Commits = []string{"foo: fix the crash"} 306 c.client.UploadBuild(build4) 307 308 // Now the bug must be fixed. 309 builderPollResp, _ = c.client.BuilderPoll(build2.Manager) 310 c.expectEQ(len(builderPollResp.PendingCommits), 0) 311 312 c.client.ReportCrash(crash1) 313 rep2 := c.client.pollBug() 314 c.expectEQ(rep2.Title, "title1 (2)") 315 } 316 317 func TestReFixedTwoManagers(t *testing.T) { 318 c := NewCtx(t) 319 defer c.Close() 320 321 build1 := testBuild(1) 322 c.client.UploadBuild(build1) 323 324 crash1 := testCrash(build1, 1) 325 c.client.ReportCrash(crash1) 326 327 builderPollResp, _ := c.client.BuilderPoll(build1.Manager) 328 c.expectEQ(len(builderPollResp.PendingCommits), 0) 329 330 rep := c.client.pollBug() 331 332 // Specify fixing commit for the bug. 333 reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ 334 ID: rep.ID, 335 Status: dashapi.BugStatusOpen, 336 FixCommits: []string{"foo: fix the crash"}, 337 }) 338 c.expectEQ(reply.OK, true) 339 340 // Now the second manager appears. 341 build2 := testBuild(2) 342 c.client.UploadBuild(build2) 343 344 // Now first manager picks up the commit. 345 build3 := testBuild(3) 346 build3.Manager = build1.Manager 347 build3.Commits = []string{"foo: fix the crash"} 348 c.client.UploadBuild(build3) 349 350 builderPollResp, _ = c.client.BuilderPoll(build1.Manager) 351 c.expectEQ(len(builderPollResp.PendingCommits), 0) 352 353 // Now we change the fixing commit. 354 reply, _ = c.client.ReportingUpdate(&dashapi.BugUpdate{ 355 ID: rep.ID, 356 Status: dashapi.BugStatusOpen, 357 FixCommits: []string{"the right one"}, 358 }) 359 c.expectEQ(reply.OK, true) 360 361 // Now it must again appear on both managers. 362 builderPollResp, _ = c.client.BuilderPoll(build1.Manager) 363 c.expectEQ(len(builderPollResp.PendingCommits), 1) 364 c.expectEQ(builderPollResp.PendingCommits[0], "the right one") 365 366 builderPollResp, _ = c.client.BuilderPoll(build1.Manager) 367 c.expectEQ(len(builderPollResp.PendingCommits), 1) 368 c.expectEQ(builderPollResp.PendingCommits[0], "the right one") 369 370 // Now the second manager picks up the second commit. 371 build4 := testBuild(4) 372 build4.Manager = build2.Manager 373 build4.Commits = []string{"the right one"} 374 c.client.UploadBuild(build4) 375 376 // The bug must be still open. 377 c.client.ReportCrash(crash1) 378 c.client.pollBugs(0) 379 380 // Specify fixing commit again, but it's the same one as before, so nothing changed. 381 reply, _ = c.client.ReportingUpdate(&dashapi.BugUpdate{ 382 ID: rep.ID, 383 Status: dashapi.BugStatusOpen, 384 FixCommits: []string{"the right one"}, 385 }) 386 c.expectEQ(reply.OK, true) 387 388 // Now the first manager picks up the second commit. 389 build5 := testBuild(5) 390 build5.Manager = build1.Manager 391 build5.Commits = []string{"the right one"} 392 c.client.UploadBuild(build5) 393 394 // Now the bug must be fixed. 395 builderPollResp, _ = c.client.BuilderPoll(build1.Manager) 396 c.expectEQ(len(builderPollResp.PendingCommits), 0) 397 398 c.client.ReportCrash(crash1) 399 rep2 := c.client.pollBug() 400 c.expectEQ(rep2.Title, "title1 (2)") 401 } 402 403 // TestFixedWithCommitTags tests fixing of bugs with Reported-by commit tags. 404 func TestFixedWithCommitTags(t *testing.T) { 405 c := NewCtx(t) 406 defer c.Close() 407 408 build1 := testBuild(1) 409 c.client.UploadBuild(build1) 410 411 build2 := testBuild(2) 412 c.client.UploadBuild(build2) 413 414 crash1 := testCrash(build1, 1) 415 c.client.ReportCrash(crash1) 416 417 rep := c.client.pollBug() 418 419 // Upload build with 2 fixing commits for this bug. 420 build1.FixCommits = []dashapi.Commit{ 421 {Title: "fix commit 1", BugIDs: []string{rep.ID}}, 422 {Title: "fix commit 2", BugIDs: []string{rep.ID}}, 423 } 424 c.client.UploadBuild(build1) 425 426 // Now the commits must be associated with the bug and the second 427 // manager must get them as pending. 428 builderPollResp, _ := c.client.BuilderPoll(build2.Manager) 429 c.expectEQ(len(builderPollResp.PendingCommits), 2) 430 c.expectEQ(builderPollResp.PendingCommits[0], "fix commit 1") 431 c.expectEQ(builderPollResp.PendingCommits[1], "fix commit 2") 432 433 // The first manager must not get them. 434 builderPollResp, _ = c.client.BuilderPoll(build1.Manager) 435 c.expectEQ(len(builderPollResp.PendingCommits), 0) 436 437 // The bug is still not fixed. 438 c.client.ReportCrash(crash1) 439 c.client.pollBugs(0) 440 441 // Now the second manager reports the same commits. 442 // This must close the bug. 443 build2.FixCommits = build1.FixCommits 444 c.client.UploadBuild(build2) 445 446 // Commits must not be passed to managers. 447 builderPollResp, _ = c.client.BuilderPoll(build2.Manager) 448 c.expectEQ(len(builderPollResp.PendingCommits), 0) 449 450 // Ensure that a new crash creates a new bug. 451 c.client.ReportCrash(crash1) 452 rep2 := c.client.pollBug() 453 c.expectEQ(rep2.Title, "title1 (2)") 454 } 455 456 // TestFixedDup tests Reported-by commit tag that comes for a dup. 457 // In such case we need to associate it with the canonical bugs. 458 func TestFixedDup(t *testing.T) { 459 c := NewCtx(t) 460 defer c.Close() 461 462 build := testBuild(1) 463 c.client.UploadBuild(build) 464 465 crash1 := testCrash(build, 1) 466 c.client.ReportCrash(crash1) 467 rep1 := c.client.pollBug() 468 469 crash2 := testCrash(build, 2) 470 c.client.ReportCrash(crash2) 471 rep2 := c.client.pollBug() 472 473 // rep2 is a dup of rep1. 474 c.client.updateBug(rep2.ID, dashapi.BugStatusDup, rep1.ID) 475 476 // Upload build that fixes rep2. 477 build.FixCommits = []dashapi.Commit{ 478 {Title: "fix commit 1", BugIDs: []string{rep2.ID}}, 479 } 480 c.client.UploadBuild(build) 481 482 // This must fix rep1. 483 c.client.ReportCrash(crash1) 484 rep3 := c.client.pollBug() 485 c.expectEQ(rep3.Title, rep1.Title+" (2)") 486 } 487 488 // TestFixedDup2 tests Reported-by commit tag that comes for a dup. 489 // Ensure that non-canonical bug gets fixing commit too. 490 func TestFixedDup2(t *testing.T) { 491 c := NewCtx(t) 492 defer c.Close() 493 494 build1 := testBuild(1) 495 c.client.UploadBuild(build1) 496 497 build2 := testBuild(2) 498 c.client.UploadBuild(build2) 499 500 crash1 := testCrash(build1, 1) 501 c.client.ReportCrash(crash1) 502 rep1 := c.client.pollBug() 503 504 crash2 := testCrash(build1, 2) 505 c.client.ReportCrash(crash2) 506 rep2 := c.client.pollBug() 507 508 // rep2 is a dup of rep1. 509 c.client.updateBug(rep2.ID, dashapi.BugStatusDup, rep1.ID) 510 511 // Upload build that fixes rep2. 512 build1.FixCommits = []dashapi.Commit{ 513 {Title: "fix commit 1", BugIDs: []string{rep2.ID}}, 514 } 515 c.client.UploadBuild(build1) 516 517 // Now undup the bugs. They are still unfixed as only 1 manager uploaded the commit. 518 c.client.updateBug(rep2.ID, dashapi.BugStatusOpen, "") 519 520 // Now the second manager reports the same commits. This must close both bugs. 521 build2.FixCommits = build1.FixCommits 522 c.client.UploadBuild(build2) 523 c.client.pollBugs(0) 524 525 c.advanceTime(24 * time.Hour) 526 c.client.ReportCrash(crash1) 527 rep3 := c.client.pollBug() 528 c.expectEQ(rep3.Title, rep1.Title+" (2)") 529 530 c.client.ReportCrash(crash2) 531 rep4 := c.client.pollBug() 532 c.expectEQ(rep4.Title, rep2.Title+" (2)") 533 } 534 535 // TestFixedDup3 tests Reported-by commit tag that comes for both dup and canonical bug. 536 func TestFixedDup3(t *testing.T) { 537 c := NewCtx(t) 538 defer c.Close() 539 540 build1 := testBuild(1) 541 c.client.UploadBuild(build1) 542 543 build2 := testBuild(2) 544 c.client.UploadBuild(build2) 545 546 crash1 := testCrash(build1, 1) 547 c.client.ReportCrash(crash1) 548 rep1 := c.client.pollBug() 549 550 crash2 := testCrash(build1, 2) 551 c.client.ReportCrash(crash2) 552 rep2 := c.client.pollBug() 553 554 // rep2 is a dup of rep1. 555 c.client.updateBug(rep2.ID, dashapi.BugStatusDup, rep1.ID) 556 557 // Upload builds that fix rep1 and rep2 with different commits. 558 // This must fix rep1 eventually and we must not livelock in such scenario. 559 build1.FixCommits = []dashapi.Commit{ 560 {Title: "fix commit 1", BugIDs: []string{rep1.ID}}, 561 {Title: "fix commit 2", BugIDs: []string{rep2.ID}}, 562 } 563 build2.FixCommits = build1.FixCommits 564 c.client.UploadBuild(build1) 565 c.client.UploadBuild(build2) 566 c.client.UploadBuild(build1) 567 c.client.UploadBuild(build2) 568 569 c.client.ReportCrash(crash1) 570 rep3 := c.client.pollBug() 571 c.expectEQ(rep3.Title, rep1.Title+" (2)") 572 }