github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/dashboard/app/discussion_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 "fmt" 8 "reflect" 9 "testing" 10 "time" 11 12 "github.com/google/go-cmp/cmp" 13 "github.com/google/syzkaller/dashboard/dashapi" 14 "github.com/google/syzkaller/pkg/email" 15 ) 16 17 func TestDiscussionAccess(t *testing.T) { 18 c := NewCtx(t) 19 defer c.Close() 20 21 client := c.makeClient(clientPublic, keyPublic, true) 22 23 build := testBuild(1) 24 client.UploadBuild(build) 25 26 // Bug at the first (AccesUser) stage of reporting. 27 crash := testCrash(build, 1) 28 client.ReportCrash(crash) 29 rep1 := client.pollBug() 30 31 // Bug at the second (AccessPublic) stage. 32 crash2 := testCrash(build, 2) 33 client.ReportCrash(crash2) 34 rep2user := client.pollBug() 35 client.updateBug(rep2user.ID, dashapi.BugStatusUpstream, "") 36 rep2 := client.pollBug() 37 38 // Patch to both bugs. 39 firstTime := timeNow(c.ctx) 40 c.advanceTime(time.Hour) 41 c.expectOK(client.SaveDiscussion(&dashapi.SaveDiscussionReq{ 42 Discussion: &dashapi.Discussion{ 43 ID: "123", 44 Source: dashapi.DiscussionLore, 45 Type: dashapi.DiscussionPatch, 46 Subject: "Patch for both bugs", 47 BugIDs: []string{rep1.ID, rep2.ID}, 48 Messages: []dashapi.DiscussionMessage{ 49 { 50 ID: "123", 51 External: true, 52 Time: firstTime, 53 }, 54 }, 55 }, 56 })) 57 58 // Discussion about the second bug. 59 secondTime := timeNow(c.ctx) 60 c.advanceTime(time.Hour) 61 c.expectOK(client.SaveDiscussion(&dashapi.SaveDiscussionReq{ 62 Discussion: &dashapi.Discussion{ 63 ID: "456", 64 Source: dashapi.DiscussionLore, 65 Type: dashapi.DiscussionReport, 66 Subject: "Second bug reported", 67 BugIDs: []string{rep2.ID}, 68 Messages: []dashapi.DiscussionMessage{ 69 { 70 ID: "456", 71 External: false, 72 Time: secondTime, 73 }, 74 }, 75 }, 76 })) 77 78 firstBug, _, err := findBugByReportingID(c.ctx, rep1.ID) 79 c.expectOK(err) 80 81 // Verify discussion that spans only one bug. 82 got, err := getBugDiscussionsUI(c.ctx, firstBug) 83 c.expectOK(err) 84 if diff := cmp.Diff([]*uiBugDiscussion{ 85 { 86 Subject: "Patch for both bugs", 87 Link: "https://lore.kernel.org/all/123/T/", 88 Total: 1, 89 External: 1, 90 Last: firstTime, 91 }, 92 }, got); diff != "" { 93 t.Fatal(diff) 94 } 95 96 secondBug, _, err := findBugByReportingID(c.ctx, rep2.ID) 97 c.expectOK(err) 98 99 // Verify that we also show discussions for several bugs. 100 got, err = getBugDiscussionsUI(c.ctx, secondBug) 101 c.expectOK(err) 102 if diff := cmp.Diff([]*uiBugDiscussion{ 103 { 104 Subject: "Second bug reported", 105 Link: "https://lore.kernel.org/all/456/T/", 106 Total: 1, 107 External: 0, 108 Last: secondTime, 109 }, 110 { 111 Subject: "Patch for both bugs", 112 Link: "https://lore.kernel.org/all/123/T/", 113 Total: 1, 114 External: 1, 115 Last: firstTime, 116 }, 117 }, got); diff != "" { 118 t.Fatal(diff) 119 } 120 121 // Verify the summary. 122 summary := secondBug.discussionSummary() 123 if diff := cmp.Diff(DiscussionSummary{ 124 AllMessages: 2, 125 ExternalMessages: 1, 126 LastMessage: secondTime, 127 LastPatchMessage: firstTime, 128 }, summary); diff != "" { 129 t.Fatal(diff) 130 } 131 } 132 133 func TestEmailOwnDiscussions(t *testing.T) { 134 c := NewCtx(t) 135 defer c.Close() 136 137 client := c.publicClient 138 139 build := testBuild(1) 140 client.UploadBuild(build) 141 142 crash := testCrash(build, 1) 143 client.ReportCrash(crash) 144 msg := client.pollEmailBug() 145 _, extBugID, err := email.RemoveAddrContext(msg.Sender) 146 c.expectOK(err) 147 148 // Start a discussion. 149 incoming1 := fmt.Sprintf(`Sender: syzkaller@googlegroups.com 150 Date: Tue, 15 Aug 2017 14:59:00 -0700 151 Message-ID: <1234> 152 Subject: Bug reported 153 From: %v 154 To: foo@bar.com, linux-kernel@vger.kernel.org 155 Content-Type: text/plain 156 157 Hello`, msg.Sender) 158 _, err = c.POST("/_ah/mail/lore@email.com", incoming1) 159 c.expectOK(err) 160 161 bug, _, err := findBugByReportingID(c.ctx, extBugID) 162 c.expectOK(err) 163 164 zone := time.FixedZone("", -7*60*60) 165 got, err := getBugDiscussionsUI(c.ctx, bug) 166 c.expectOK(err) 167 if diff := cmp.Diff([]*uiBugDiscussion{ 168 { 169 Subject: "Bug reported", 170 Link: "https://lore.kernel.org/all/1234/T/", 171 Total: 1, 172 External: 0, 173 Last: time.Date(2017, time.August, 15, 14, 59, 0, 0, zone), 174 }, 175 }, got); diff != "" { 176 t.Fatal(diff) 177 } 178 179 // Emulate some user-reply to the discussion. 180 incoming2 := fmt.Sprintf(`Sender: user@user.com 181 Date: Tue, 16 Aug 2017 14:59:00 -0700 182 Message-ID: <2345> 183 Subject: Re. Bug reported 184 From: user@user.com 185 In-Reply-To: <1234> 186 Cc: %v, linux-kernel@vger.kernel.org 187 Content-Type: text/plain 188 189 Hello`, msg.Sender) 190 _, err = c.POST("/_ah/mail/lore@email.com", incoming2) 191 c.expectOK(err) 192 193 bug, _, err = findBugByReportingID(c.ctx, extBugID) 194 c.expectOK(err) 195 196 got, err = getBugDiscussionsUI(c.ctx, bug) 197 c.expectOK(err) 198 if diff := cmp.Diff([]*uiBugDiscussion{ 199 { 200 Subject: "Bug reported", 201 Link: "https://lore.kernel.org/all/1234/T/", 202 Total: 2, 203 External: 1, 204 Last: time.Date(2017, time.August, 16, 14, 59, 0, 0, zone), 205 }, 206 }, got); diff != "" { 207 t.Fatal(diff) 208 } 209 } 210 211 func TestEmailUnrelatedDiscussion(t *testing.T) { 212 c := NewCtx(t) 213 defer c.Close() 214 215 client := c.publicClient 216 217 build := testBuild(1) 218 client.UploadBuild(build) 219 220 crash := testCrash(build, 1) 221 client.ReportCrash(crash) 222 msg := client.pollEmailBug() 223 _, extBugID, err := email.RemoveAddrContext(msg.Sender) 224 c.expectOK(err) 225 226 // An email that's not sent to the target email address. 227 incoming1 := fmt.Sprintf(`Date: Tue, 15 Aug 2017 14:59:00 -0700 228 Message-ID: <1234> 229 Subject: Some discussion 230 In-Reply-To: <2345> 231 From: user@user.com 232 To: %v, lore@email.com 233 Content-Type: text/plain 234 235 Hello`, msg.Sender) 236 _, err = c.POST("/_ah/mail/"+msg.Sender, incoming1) 237 c.expectOK(err) 238 239 bug, _, err := findBugByReportingID(c.ctx, extBugID) 240 c.expectOK(err) 241 242 // The discussion should go ignored. 243 got, err := getBugDiscussionsUI(c.ctx, bug) 244 c.expectOK(err) 245 if diff := cmp.Diff([]*uiBugDiscussion(nil), got); diff != "" { 246 t.Fatal(diff) 247 } 248 } 249 250 func TestEmailSubdiscussion(t *testing.T) { 251 c := NewCtx(t) 252 defer c.Close() 253 254 client := c.publicClient 255 256 build := testBuild(1) 257 client.UploadBuild(build) 258 259 crash := testCrash(build, 1) 260 client.ReportCrash(crash) 261 msg := client.pollEmailBug() 262 _, extBugID, err := email.RemoveAddrContext(msg.Sender) 263 c.expectOK(err) 264 265 incoming1 := fmt.Sprintf(`Date: Tue, 15 Aug 2017 14:59:00 -0700 266 Message-ID: <2345> 267 Subject: Some discussion 268 In-Reply-To: <1234> 269 From: user@user.com 270 To: %v 271 Cc: lore@email.com 272 Content-Type: text/plain 273 274 Hello`, msg.Sender) 275 _, err = c.POST("/_ah/mail/lore@email.com", incoming1) 276 c.expectOK(err) 277 278 bug, _, err := findBugByReportingID(c.ctx, extBugID) 279 c.expectOK(err) 280 281 // We have not seen the start of the discussion, but it should not go ignored. 282 got, err := getBugDiscussionsUI(c.ctx, bug) 283 c.expectOK(err) 284 client.expectEQ(len(got), 1) 285 client.expectEQ(got[0].Link, "https://lore.kernel.org/all/2345/T/") 286 } 287 288 func TestEmailPatchWithLink(t *testing.T) { 289 c := NewCtx(t) 290 defer c.Close() 291 292 client := c.publicClient 293 294 build := testBuild(1) 295 client.UploadBuild(build) 296 297 crash := testCrash(build, 1) 298 client.ReportCrash(crash) 299 msg := client.pollEmailBug() 300 _, extBugID, err := email.RemoveAddrContext(msg.Sender) 301 c.expectOK(err) 302 303 incoming1 := fmt.Sprintf(`Date: Tue, 15 Aug 2017 14:59:00 -0700 304 Message-ID: <2345> 305 Subject: [PATCH v3] A lot of fixes 306 From: user@user.com 307 To: lore@email.com 308 Content-Type: text/plain 309 310 Hello, 311 312 Link: https://testapp.appspot.com/bug?extid=%v 313 `, extBugID) 314 _, err = c.POST("/_ah/mail/lore@email.com", incoming1) 315 c.expectOK(err) 316 317 bug, _, err := findBugByReportingID(c.ctx, extBugID) 318 c.expectOK(err) 319 320 // We have not seen the start of the discussion, but it should not go ignored. 321 got, err := getBugDiscussionsUI(c.ctx, bug) 322 c.expectOK(err) 323 client.expectEQ(len(got), 1) 324 client.expectEQ(got[0].Link, "https://lore.kernel.org/all/2345/T/") 325 client.expectEQ(got[0].Subject, "[PATCH v3] A lot of fixes") 326 } 327 328 func TestIgnoreBotReplies(t *testing.T) { 329 c := NewCtx(t) 330 defer c.Close() 331 332 client := c.publicClient 333 334 build := testBuild(1) 335 client.UploadBuild(build) 336 337 crash := testCrash(build, 1) 338 client.ReportCrash(crash) 339 msg := client.pollEmailBug() 340 _, extBugID, err := email.RemoveAddrContext(msg.Sender) 341 c.expectOK(err) 342 343 incoming1 := fmt.Sprintf(`Date: Tue, 15 Aug 2017 14:59:00 -0700 344 Message-ID: <2345> 345 Subject: Re: Patch testing request 346 From: %v 347 To: lore@email.com 348 In-Reply-To: <1234> 349 Content-Type: text/plain 350 351 Hello! 352 `, msg.Sender) 353 _, err = c.POST("/_ah/mail/lore@email.com", incoming1) 354 c.expectOK(err) 355 356 bug, _, err := findBugByReportingID(c.ctx, extBugID) 357 c.expectOK(err) 358 359 // We have not seen the start of the discussion, but it should not go ignored. 360 got, err := getBugDiscussionsUI(c.ctx, bug) 361 c.expectOK(err) 362 client.expectEQ(len(got), 0) 363 } 364 365 func TestMessageOverflow(t *testing.T) { 366 date := time.Date(2000, time.January, 1, 1, 0, 0, 0, time.UTC) 367 d := &Discussion{} 368 first, last := dashapi.DiscussionMessage{ 369 ID: date.String(), 370 Time: date, 371 }, dashapi.DiscussionMessage{} 372 d.addMessages([]dashapi.DiscussionMessage{first}) 373 374 const blockSize = 100 375 for i := 0; i < 2*maxMessagesInDiscussion; i += blockSize { 376 block := []dashapi.DiscussionMessage{} 377 for j := 0; j < blockSize; j++ { 378 date = date.Add(time.Minute) 379 last = dashapi.DiscussionMessage{ 380 ID: date.String(), 381 Time: date, 382 } 383 block = append(block, last) 384 } 385 // Make sure that the first message always remains in place and the last one 386 // is the latest one. 387 d.addMessages(block) 388 if !reflect.DeepEqual(first.ID, d.Messages[0].ID) { 389 t.Fatalf("unexpected first messages") 390 } 391 if !reflect.DeepEqual(last.ID, d.Messages[len(d.Messages)-1].ID) { 392 t.Fatalf("unexpected last messages") 393 } 394 } 395 if len(d.Messages) != maxMessagesInDiscussion { 396 t.Fatalf("expected len to be equal to %d, got %d", 397 maxMessagesInDiscussion, len(d.Messages)) 398 } 399 }