github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/dashboard/app/main_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 "bytes" 8 "context" 9 "errors" 10 "net/http" 11 "testing" 12 "time" 13 14 "github.com/google/syzkaller/dashboard/dashapi" 15 "github.com/stretchr/testify/assert" 16 ) 17 18 func TestOnlyManagerFilter(t *testing.T) { 19 c := NewCtx(t) 20 defer c.Close() 21 22 client := c.client 23 build1 := testBuild(1) 24 client.UploadBuild(build1) 25 build2 := testBuild(2) 26 client.UploadBuild(build2) 27 28 crash1 := testCrash(build1, 1) 29 crash1.Title = "only the first manager" 30 client.ReportCrash(crash1) 31 32 crash2 := testCrash(build2, 2) 33 crash2.Title = "only the second manager" 34 client.ReportCrash(crash2) 35 36 crashBoth1 := testCrash(build1, 3) 37 crashBoth1.Title = "both managers" 38 client.ReportCrash(crashBoth1) 39 40 crashBoth2 := testCrash(build2, 4) 41 crashBoth2.Title = "both managers" 42 client.ReportCrash(crashBoth2) 43 44 // Make sure all those bugs are present on the main page. 45 reply, err := c.AuthGET(AccessAdmin, "/test1") 46 c.expectOK(err) 47 for _, title := range []string{crash1.Title, crash2.Title, crashBoth1.Title} { 48 if !bytes.Contains(reply, []byte(title)) { 49 t.Fatalf("%#v is not contained on the main page", title) 50 } 51 } 52 53 // Check that filtering on the main page works. 54 reply, err = c.AuthGET(AccessAdmin, "/test1?only_manager="+build1.Manager) 55 c.expectOK(err) 56 for _, title := range []string{crash2.Title, crashBoth1.Title} { 57 if bytes.Contains(reply, []byte(title)) { 58 t.Fatalf("%#v is contained on the main page", title) 59 } 60 } 61 if !bytes.Contains(reply, []byte(crash1.Title)) { 62 t.Fatalf("%#v is not contained on the main page", crash1.Title) 63 } 64 65 // Invalidate all these bugs. 66 polledBugs := client.pollBugs(3) 67 for _, bug := range polledBugs { 68 client.updateBug(bug.ID, dashapi.BugStatusInvalid, "") 69 } 70 71 // Verify that the filtering works on the invalid bugs page. 72 reply, err = c.AuthGET(AccessAdmin, "/test1/invalid?only_manager="+build2.Manager) 73 c.expectOK(err) 74 for _, title := range []string{crash1.Title, crashBoth1.Title} { 75 if bytes.Contains(reply, []byte(title)) { 76 t.Fatalf("%#v is contained on the invalid bugs page", title) 77 } 78 } 79 if !bytes.Contains(reply, []byte(crash2.Title)) { 80 t.Fatalf("%#v is not contained on the invalid bugs page", crash2.Title) 81 } 82 } 83 84 const ( 85 subsystemA = "subsystemA" 86 subsystemB = "subsystemB" 87 ) 88 89 func TestSubsystemFilterMain(t *testing.T) { 90 c := NewCtx(t) 91 defer c.Close() 92 93 client := c.client 94 build := testBuild(1) 95 client.UploadBuild(build) 96 97 crash1 := testCrash(build, 1) 98 crash1.Title = "first bug" 99 crash1.GuiltyFiles = []string{"a.c"} 100 client.ReportCrash(crash1) 101 102 crash2 := testCrash(build, 2) 103 crash2.Title = "second bug" 104 crash2.GuiltyFiles = []string{"b.c"} 105 client.ReportCrash(crash2) 106 107 client.pollBugs(2) 108 // Make sure all those bugs are present on the main page. 109 reply, err := c.AuthGET(AccessAdmin, "/test1") 110 c.expectOK(err) 111 for _, title := range []string{crash1.Title, crash2.Title} { 112 if !bytes.Contains(reply, []byte(title)) { 113 t.Fatalf("%#v is not contained on the main page", title) 114 } 115 } 116 // Check that filtering on the main page works. 117 reply, err = c.AuthGET(AccessAdmin, "/test1?label=subsystems:"+subsystemA) 118 c.expectOK(err) 119 for _, title := range []string{crash2.Title} { 120 if bytes.Contains(reply, []byte(title)) { 121 t.Fatalf("%#v is contained on the main page", title) 122 } 123 } 124 if !bytes.Contains(reply, []byte(crash1.Title)) { 125 t.Fatalf("%#v is not contained on the main page", crash2.Title) 126 } 127 } 128 129 func TestSubsystemFilterTerminal(t *testing.T) { 130 c := NewCtx(t) 131 defer c.Close() 132 133 client := c.client 134 build := testBuild(1) 135 client.UploadBuild(build) 136 137 crash1 := testCrash(build, 1) 138 crash1.Title = "first bug" 139 crash1.GuiltyFiles = []string{"a.c"} 140 client.ReportCrash(crash1) 141 142 crash2 := testCrash(build, 2) 143 crash2.Title = "second bug" 144 crash2.GuiltyFiles = []string{"b.c"} 145 client.ReportCrash(crash2) 146 147 // Invalidate all these bugs. 148 polledBugs := client.pollBugs(2) 149 for _, bug := range polledBugs { 150 client.updateBug(bug.ID, dashapi.BugStatusInvalid, "") 151 } 152 153 // Verify that the filtering works on the invalid bugs page. 154 reply, err := c.AuthGET(AccessAdmin, "/test1/invalid?label=subsystems:"+subsystemB) 155 c.expectOK(err) 156 for _, title := range []string{crash1.Title} { 157 if bytes.Contains(reply, []byte(title)) { 158 t.Fatalf("%#v is contained on the invalid bugs page", title) 159 } 160 } 161 if !bytes.Contains(reply, []byte(crash2.Title)) { 162 t.Fatalf("%#v is not contained on the invalid bugs page", crash2.Title) 163 } 164 } 165 166 func TestMainBugFilters(t *testing.T) { 167 c := NewCtx(t) 168 defer c.Close() 169 170 client := c.client 171 build1 := testBuild(1) 172 build1.Manager = "manager-name-123" 173 client.UploadBuild(build1) 174 175 crash1 := testCrash(build1, 1) 176 crash1.Title = "my-crash-title" 177 client.ReportCrash(crash1) 178 client.pollBugs(1) 179 180 // The normal main page. 181 reply, err := c.AuthGET(AccessAdmin, "/test1") 182 c.expectOK(err) 183 assert.Contains(t, string(reply), build1.Manager) 184 assert.NotContains(t, string(reply), "Applied filters") 185 186 reply, err = c.AuthGET(AccessAdmin, "/test1?label=subsystems:abcd") 187 c.expectOK(err) 188 assert.NotContains(t, string(reply), build1.Manager) // managers are hidden 189 assert.Contains(t, string(reply), "Applied filters") // we're seeing a prompt to disable the filter 190 assert.NotContains(t, string(reply), crash1.Title) // the bug does not belong to the subsystem 191 192 reply, err = c.AuthGET(AccessAdmin, "/test1?no_subsystem=true") 193 c.expectOK(err) 194 assert.Contains(t, string(reply), crash1.Title) // the bug has no subsystems 195 } 196 197 func TestSubsystemsList(t *testing.T) { 198 c := NewCtx(t) 199 defer c.Close() 200 201 client := c.client 202 build := testBuild(1) 203 client.UploadBuild(build) 204 205 crash1 := testCrash(build, 1) 206 crash1.GuiltyFiles = []string{"a.c"} 207 client.ReportCrash(crash1) 208 client.pollBug() 209 210 crash2 := testCrash(build, 2) 211 crash2.GuiltyFiles = []string{"b.c"} 212 client.ReportCrash(crash2) 213 client.updateBug(client.pollBug().ID, dashapi.BugStatusInvalid, "") 214 215 _, err := c.AuthGET(AccessUser, "/cron/refresh_subsystems") 216 c.expectOK(err) 217 218 reply, err := c.AuthGET(AccessAdmin, "/test1/subsystems") 219 c.expectOK(err) 220 assert.Contains(t, string(reply), "subsystemA") 221 assert.NotContains(t, string(reply), "subsystemB") 222 223 reply, err = c.AuthGET(AccessAdmin, "/test1/subsystems?all=true") 224 c.expectOK(err) 225 assert.Contains(t, string(reply), "subsystemA") 226 assert.Contains(t, string(reply), "subsystemB") 227 } 228 229 func TestSubsystemPage(t *testing.T) { 230 c := NewCtx(t) 231 defer c.Close() 232 233 client := c.client 234 build := testBuild(1) 235 client.UploadBuild(build) 236 237 crash1 := testCrash(build, 1) 238 crash1.Title = "test crash title" 239 crash1.GuiltyFiles = []string{"a.c"} 240 client.ReportCrash(crash1) 241 client.pollBug() 242 243 crash2 := testCrash(build, 2) 244 crash2.GuiltyFiles = []string{"b.c"} 245 client.ReportCrash(crash2) 246 crash2.Title = "crash that must not be present" 247 client.updateBug(client.pollBug().ID, dashapi.BugStatusInvalid, "") 248 249 reply, err := c.AuthGET(AccessAdmin, "/test1/s/subsystemA") 250 c.expectOK(err) 251 assert.Contains(t, string(reply), crash1.Title) 252 assert.NotContains(t, string(reply), crash2.Title) 253 } 254 255 func TestMultiLabelFilter(t *testing.T) { 256 c := NewCtx(t) 257 defer c.Close() 258 259 client := c.makeClient(clientPublicEmail, keyPublicEmail, true) 260 mailingList := c.config().Namespaces["access-public-email"].Reporting[0].Config.(*EmailConfig).Email 261 262 build1 := testBuild(1) 263 build1.Manager = "manager-name-123" 264 client.UploadBuild(build1) 265 266 crash1 := testCrash(build1, 1) 267 crash1.GuiltyFiles = []string{"a.c"} 268 crash1.Title = "crash-with-subsystem-A" 269 client.ReportCrash(crash1) 270 c.pollEmailBug() 271 272 crash2 := testCrash(build1, 2) 273 crash2.GuiltyFiles = []string{"a.c"} 274 crash2.Title = "prio-crash-subsystem-A" 275 client.ReportCrash(crash2) 276 277 c.incomingEmail(c.pollEmailBug().Sender, "#syz set prio: low\n", 278 EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList})) 279 280 // The normal main page. 281 reply, err := c.AuthGET(AccessAdmin, "/access-public-email") 282 c.expectOK(err) 283 assert.Contains(t, string(reply), build1.Manager) 284 assert.NotContains(t, string(reply), "Applied filters") 285 286 reply, err = c.AuthGET(AccessAdmin, "/access-public-email?label=subsystems:subsystemA") 287 c.expectOK(err) 288 assert.Contains(t, string(reply), "Applied filters") // we're seeing a prompt to disable the filter 289 assert.Contains(t, string(reply), crash1.Title) 290 assert.Contains(t, string(reply), crash2.Title) 291 292 // Test filters together. 293 reply, err = c.AuthGET(AccessAdmin, "/access-public-email?label=subsystems:subsystemA&&label=prio:low") 294 c.expectOK(err) 295 assert.NotContains(t, string(reply), crash1.Title) 296 assert.Contains(t, string(reply), crash2.Title) 297 298 // Ensure we provide links that drop labels. 299 assert.NotContains(t, string(reply), "/access-public-email?label=subsystems:subsystemA\"") 300 assert.NotContains(t, string(reply), "/access-public-email?label=prop:low\"") 301 } 302 303 func TestAdminJobList(t *testing.T) { 304 c := NewCtx(t) 305 defer c.Close() 306 307 client := c.client2 308 build := testBuild(1) 309 client.UploadBuild(build) 310 311 crash := testCrash(build, 1) 312 crash.Title = "some bug title" 313 crash.GuiltyFiles = []string{"a.c"} 314 crash.ReproOpts = []byte("repro opts") 315 crash.ReproSyz = []byte("repro syz") 316 crash.ReproC = []byte("repro C") 317 client.ReportCrash(crash) 318 client.pollEmailBug() 319 320 c.advanceTime(24 * time.Hour) 321 322 pollResp := client.pollSpecificJobs(build.Manager, dashapi.ManagerJobs{BisectCause: true}) 323 c.expectNE(pollResp.ID, "") 324 325 causeJobsLink := "/admin?job_type=1" 326 fixJobsLink := "/admin?job_type=2" 327 reply, err := c.AuthGET(AccessAdmin, "/admin") 328 c.expectOK(err) 329 assert.Contains(t, string(reply), causeJobsLink) 330 assert.Contains(t, string(reply), fixJobsLink) 331 332 // Verify the bug is in the bisect cause jobs list. 333 reply, err = c.AuthGET(AccessAdmin, causeJobsLink) 334 c.expectOK(err) 335 assert.Contains(t, string(reply), crash.Title) 336 337 // Verify the bug is NOT in the fix jobs list. 338 reply, err = c.AuthGET(AccessAdmin, fixJobsLink) 339 c.expectOK(err) 340 assert.NotContains(t, string(reply), crash.Title) 341 } 342 343 func TestSubsystemsPageRedirect(t *testing.T) { 344 c := NewCtx(t) 345 defer c.Close() 346 347 // Verify that the normal subsystem page works. 348 _, err := c.AuthGET(AccessAdmin, "/access-public-email/s/subsystemA") 349 c.expectOK(err) 350 351 // Verify that the old subsystem name points to the new one. 352 _, err = c.AuthGET(AccessAdmin, "/access-public-email/s/oldSubsystem") 353 var httpErr *HTTPError 354 c.expectTrue(errors.As(err, &httpErr)) 355 c.expectEQ(httpErr.Code, http.StatusMovedPermanently) 356 c.expectEQ(httpErr.Headers["Location"], []string{"/access-public-email/s/subsystemA"}) 357 } 358 359 func TestNoThrottle(t *testing.T) { 360 c := NewCtx(t) 361 defer c.Close() 362 363 assert.True(t, c.config().Throttle.Empty()) 364 for i := 0; i < 10; i++ { 365 c.advanceTime(time.Millisecond) 366 _, err := c.AuthGET(AccessPublic, "/access-public-email") 367 c.expectOK(err) 368 } 369 } 370 371 func TestThrottle(t *testing.T) { 372 c := NewCtx(t) 373 defer c.Close() 374 375 c.transformContext = func(c context.Context) context.Context { 376 newConfig := *getConfig(c) 377 newConfig.Throttle = ThrottleConfig{ 378 Window: 10 * time.Second, 379 Limit: 10, 380 } 381 return contextWithConfig(c, &newConfig) 382 } 383 384 // Adhere to the limit. 385 for i := 0; i < 15; i++ { 386 c.advanceTime(time.Second) 387 _, err := c.AuthGET(AccessPublic, "/access-public-email") 388 c.expectOK(err) 389 } 390 391 // Break the limit. 392 c.advanceTime(time.Millisecond) 393 _, err := c.AuthGET(AccessPublic, "/access-public-email") 394 var httpErr *HTTPError 395 c.expectTrue(errors.As(err, &httpErr)) 396 c.expectEQ(httpErr.Code, http.StatusTooManyRequests) 397 398 // Still too frequent requests. 399 c.advanceTime(time.Millisecond) 400 _, err = c.AuthGET(AccessPublic, "/access-public-email") 401 c.expectTrue(err != nil) 402 403 // Wait a bit. 404 c.advanceTime(3 * time.Second) 405 _, err = c.AuthGET(AccessPublic, "/access-public-email") 406 c.expectOK(err) 407 } 408 409 func TestManagerPage(t *testing.T) { 410 c := NewCtx(t) 411 defer c.Close() 412 413 const firstManager = "manager-name" 414 const secondManager = "another-manager-name" 415 416 client := c.makeClient(clientPublicEmail, keyPublicEmail, true) 417 build1 := testBuild(1) 418 build1.Manager = firstManager 419 c.expectOK(client.UploadBuild(build1)) 420 421 c.advanceTime(time.Hour) 422 build2 := testBuild(2) 423 build2.Manager = firstManager 424 buildErrorReq := &dashapi.BuildErrorReq{ 425 Build: *build2, 426 Crash: dashapi.Crash{ 427 Title: "failed build 1", 428 Report: []byte("report\n"), 429 Log: []byte("log\n"), 430 }, 431 } 432 c.expectOK(client.ReportBuildError(buildErrorReq)) 433 c.pollEmailBug() 434 435 c.advanceTime(time.Hour) 436 build3 := testBuild(3) 437 build3.Manager = firstManager 438 c.expectOK(client.UploadBuild(build3)) 439 440 // And one more build from a different manager. 441 c.advanceTime(time.Hour) 442 build4 := testBuild(4) 443 build4.Manager = secondManager 444 c.expectOK(client.UploadBuild(build4)) 445 446 // Query the first manager. 447 reply, err := c.AuthGET(AccessPublic, "/access-public-email/manager/"+firstManager) 448 c.expectOK(err) 449 assert.Contains(t, string(reply), "kernel_commit_title1") 450 assert.NotContains(t, string(reply), "kernel_commit_title2") // build error 451 assert.Contains(t, string(reply), "kernel_commit_title3") 452 assert.NotContains(t, string(reply), "kernel_commit_title4") // another manager 453 454 // Query the second manager. 455 reply, err = c.AuthGET(AccessPublic, "/access-public-email/manager/"+secondManager) 456 c.expectOK(err) 457 assert.NotContains(t, string(reply), "kernel_commit_title1") 458 assert.NotContains(t, string(reply), "kernel_commit_title2") 459 assert.NotContains(t, string(reply), "kernel_commit_title3") 460 assert.Contains(t, string(reply), "kernel_commit_title4") // another manager 461 462 // Query unknown manager. 463 _, err = c.AuthGET(AccessPublic, "/access-public-email/manager/abcd") 464 var httpErr *HTTPError 465 c.expectTrue(errors.As(err, &httpErr)) 466 c.expectEQ(httpErr.Code, http.StatusBadRequest) 467 }