github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/email/lore/parse_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 lore 5 6 import ( 7 "fmt" 8 "sort" 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 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 ) 18 19 func TestThreadsCollection(t *testing.T) { 20 messages := []string{ 21 // <A-Base> <-- <A-Child-1> <-- <A-Child-1-1>. 22 `Date: Sun, 7 May 2017 19:54:00 -0700 23 Subject: Thread A 24 Message-ID: <A-Base> 25 From: UserA <a@user.com> 26 Content-Type: text/plain 27 28 29 Some text`, 30 `Date: Sun, 7 May 2017 19:55:00 -0700 31 Subject: Re: Thread A 32 Message-ID: <A-Child-1> 33 From: UserB <b@user.com> 34 To: UserA <a@user.com> 35 Content-Type: text/plain 36 In-Reply-To: <A-Base> 37 38 39 Some reply`, 40 `Date: Sun, 7 May 2017 19:56:00 -0700 41 Subject: Re: Re: Thread A 42 Message-ID: <A-Child-1-1> 43 From: UserC <c@user.com> 44 To: UserA <a@user.com>, UserB <b@user.com> 45 Content-Type: text/plain 46 In-Reply-To: <A-Child-1> 47 48 49 Some reply (2)`, 50 // <Bug> with two children: <Bug-Reply1>, <Bug-Reply2>. 51 `Date: Sun, 7 May 2017 19:57:00 -0700 52 Subject: [syzbot] Some bug 53 Message-ID: <Bug> 54 From: syzbot <syzbot+4564456@bar.com> 55 Content-Type: text/plain 56 57 58 Bug report`, 59 `Date: Sun, 7 May 2017 19:58:00 -0700 60 Subject: Re: [syzbot] Some bug 61 Message-ID: <Bug-Reply1> 62 From: UserC <c@user.com> 63 To: syzbot <syzbot+4564456@bar.com> 64 In-Reply-To: <Bug> 65 Content-Type: text/plain 66 67 68 Bug report reply`, 69 `Date: Sun, 7 May 2017 19:58:01 -0700 70 Subject: Re: [syzbot] Some bug 71 Message-ID: <Bug-Reply2> 72 From: UserD <d@user.com> 73 To: syzbot <syzbot+4564456@bar.com> 74 In-Reply-To: <Bug>B 75 Content-Type: text/plain 76 77 78 Bug report reply 2`, 79 // And one PATCH without replies. 80 `Date: Sun, 7 May 2017 19:58:01 -0700 81 Subject: [PATCH] Some bug fixed 82 Message-ID: <Patch> 83 From: UserE <e@user.com> 84 Cc: syzbot <syzbot+12345@bar.com> 85 Content-Type: text/plain 86 87 88 Patch`, 89 // An orphaned reply from a human. 90 `Date: Sun, 7 May 2017 19:57:00 -0700 91 Subject: Another bug discussion 92 In-Reply-To: <Unknown> 93 Message-ID: <Sub-Discussion> 94 From: person@email.com 95 Cc: syzbot <syzbot+4564456@bar.com> 96 Content-Type: text/plain 97 98 99 Bug report`, 100 // An orphaned reply from a bot. 101 `Date: Sun, 7 May 2017 19:57:00 -0700 102 Subject: Re: [syzbot] Some bug 3 103 In-Reply-To: <Unknown> 104 Message-ID: <Sub-Discussion-Bot> 105 From: syzbot+4564456@bar.com 106 To: all@email.com 107 Content-Type: text/plain 108 109 110 Bug report`, 111 } 112 113 zone := time.FixedZone("", -7*60*60) 114 expected := map[string]*Thread{ 115 "<A-Base>": { 116 Subject: "Thread A", 117 MessageID: "<A-Base>", 118 Type: dashapi.DiscussionMention, 119 Messages: []*Email{ 120 { 121 Email: &email.Email{ 122 MessageID: "<A-Base>", 123 Subject: "Thread A", 124 Date: time.Date(2017, time.May, 7, 19, 54, 0, 0, zone), 125 Author: "a@user.com", 126 Cc: []string{"a@user.com"}, 127 }, 128 }, 129 { 130 Email: &email.Email{ 131 MessageID: "<A-Child-1>", 132 Subject: "Re: Thread A", 133 Date: time.Date(2017, time.May, 7, 19, 55, 0, 0, zone), 134 Author: "b@user.com", 135 Cc: []string{"a@user.com", "b@user.com"}, 136 InReplyTo: "<A-Base>", 137 }, 138 }, 139 { 140 Email: &email.Email{ 141 MessageID: "<A-Child-1-1>", 142 Subject: "Re: Re: Thread A", 143 Date: time.Date(2017, time.May, 7, 19, 56, 0, 0, zone), 144 Author: "c@user.com", 145 Cc: []string{"a@user.com", "b@user.com", "c@user.com"}, 146 InReplyTo: "<A-Child-1>", 147 }, 148 }, 149 }, 150 }, 151 "<Bug>": { 152 Subject: "[syzbot] Some bug", 153 MessageID: "<Bug>", 154 Type: dashapi.DiscussionReport, 155 BugIDs: []string{"4564456"}, 156 Messages: []*Email{ 157 { 158 Email: &email.Email{ 159 MessageID: "<Bug>", 160 BugIDs: []string{"4564456"}, 161 Subject: "[syzbot] Some bug", 162 Date: time.Date(2017, time.May, 7, 19, 57, 0, 0, zone), 163 Author: "syzbot@bar.com", 164 OwnEmail: true, 165 }, 166 }, 167 { 168 Email: &email.Email{ 169 MessageID: "<Bug-Reply1>", 170 BugIDs: []string{"4564456"}, 171 Subject: "Re: [syzbot] Some bug", 172 Date: time.Date(2017, time.May, 7, 19, 58, 0, 0, zone), 173 Author: "c@user.com", 174 Cc: []string{"c@user.com"}, 175 InReplyTo: "<Bug>", 176 }, 177 }, 178 { 179 Email: &email.Email{ 180 MessageID: "<Bug-Reply2>", 181 BugIDs: []string{"4564456"}, 182 Subject: "Re: [syzbot] Some bug", 183 Date: time.Date(2017, time.May, 7, 19, 58, 1, 0, zone), 184 Author: "d@user.com", 185 Cc: []string{"d@user.com"}, 186 InReplyTo: "<Bug>", 187 }, 188 }, 189 }, 190 }, 191 "<Patch>": { 192 Subject: "[PATCH] Some bug fixed", 193 MessageID: "<Patch>", 194 Type: dashapi.DiscussionPatch, 195 BugIDs: []string{"12345"}, 196 Messages: []*Email{ 197 { 198 Email: &email.Email{ 199 MessageID: "<Patch>", 200 BugIDs: []string{"12345"}, 201 Subject: "[PATCH] Some bug fixed", 202 Date: time.Date(2017, time.May, 7, 19, 58, 1, 0, zone), 203 Author: "e@user.com", 204 Cc: []string{"e@user.com"}, 205 }, 206 }, 207 }, 208 }, 209 "<Sub-Discussion>": { 210 Subject: "Another bug discussion", 211 MessageID: "<Sub-Discussion>", 212 Type: dashapi.DiscussionMention, 213 BugIDs: []string{"4564456"}, 214 Messages: []*Email{ 215 { 216 Email: &email.Email{ 217 MessageID: "<Sub-Discussion>", 218 InReplyTo: "<Unknown>", 219 Date: time.Date(2017, time.May, 7, 19, 57, 0, 0, zone), 220 BugIDs: []string{"4564456"}, 221 Cc: []string{"person@email.com"}, 222 Subject: "Another bug discussion", 223 Author: "person@email.com", 224 }, 225 }, 226 }, 227 }, 228 "<Sub-Discussion-Bot>": nil, 229 } 230 231 var emails []*Email 232 for _, m := range messages { 233 msg, err := emailFromRaw([]byte(m), []string{"syzbot@bar.com"}, []string{"bar.com"}) 234 if err != nil { 235 t.Fatal(err) 236 } 237 msg.RawCc = nil 238 emails = append(emails, msg) 239 } 240 241 threads := Threads(emails) 242 got := map[string]*Thread{} 243 244 for _, d := range threads { 245 sort.Slice(d.Messages, func(i, j int) bool { 246 return d.Messages[i].Date.Before(d.Messages[j].Date) 247 }) 248 got[d.MessageID] = d 249 } 250 251 for key, val := range expected { 252 if diff := cmp.Diff(val, got[key]); diff != "" { 253 t.Fatalf("%s: %s", key, diff) 254 } 255 } 256 257 if len(threads) > len(expected) { 258 t.Fatalf("expected %d threads, got %d", len(expected), len(threads)) 259 } 260 } 261 262 func TestParsePatchSubject(t *testing.T) { 263 tests := []struct { 264 subj string 265 ret PatchSubject 266 }{ 267 { 268 subj: `[PATCH] abcd`, 269 ret: PatchSubject{Title: "abcd"}, 270 }, 271 { 272 subj: `[PATCH 00/20] abcd`, 273 ret: PatchSubject{Title: "abcd", Seq: value[int](0), Total: value[int](20)}, 274 }, 275 { 276 subj: `[PATCH 5/6] abcd`, 277 ret: PatchSubject{Title: "abcd", Seq: value[int](5), Total: value[int](6)}, 278 }, 279 { 280 subj: `[PATCH RFC v3 0/4] abcd`, 281 ret: PatchSubject{ 282 Title: "abcd", 283 Tags: []string{"RFC"}, 284 Version: value[int](3), 285 Seq: value[int](0), 286 Total: value[int](4), 287 }, 288 }, 289 { 290 subj: `[RFC PATCH] abcd`, 291 ret: PatchSubject{Title: "abcd", Tags: []string{"RFC"}}, 292 }, 293 { 294 subj: `[PATCH net-next v2 00/21] abcd`, 295 ret: PatchSubject{ 296 Title: "abcd", 297 Tags: []string{"net-next"}, 298 Version: value[int](2), 299 Seq: value[int](0), 300 Total: value[int](21), 301 }, 302 }, 303 { 304 subj: `[PATCH v2 RESEND] abcd`, 305 ret: PatchSubject{Title: "abcd", Version: value[int](2), Tags: []string{"RESEND"}}, 306 }, 307 { 308 subj: `[PATCH RFC net-next v3 05/21] abcd`, 309 ret: PatchSubject{ 310 Title: "abcd", 311 Tags: []string{"RFC", "net-next"}, 312 Version: value[int](3), 313 Seq: value[int](5), 314 Total: value[int](21), 315 }, 316 }, 317 } 318 for id, test := range tests { 319 t.Run(fmt.Sprint(id), func(t *testing.T) { 320 ret, ok := parsePatchSubject(test.subj) 321 assert.True(t, ok) 322 assert.Equal(t, test.ret, ret) 323 }) 324 } 325 } 326 327 func TestDiscussionType(t *testing.T) { 328 tests := []struct { 329 msg *email.Email 330 ret dashapi.DiscussionType 331 }{ 332 { 333 msg: &email.Email{ 334 Subject: "[PATCH] Bla-bla", 335 }, 336 ret: dashapi.DiscussionPatch, 337 }, 338 { 339 msg: &email.Email{ 340 Subject: "[patch v3] Bla-bla", 341 }, 342 ret: dashapi.DiscussionPatch, 343 }, 344 { 345 msg: &email.Email{ 346 Subject: "[RFC PATCH] Bla-bla", 347 }, 348 ret: dashapi.DiscussionPatch, 349 }, 350 { 351 msg: &email.Email{ 352 Subject: "[RESEND PATCH] Bla-bla", 353 }, 354 ret: dashapi.DiscussionPatch, 355 }, 356 { 357 msg: &email.Email{ 358 Subject: "[syzbot] Monthly ext4 report", 359 OwnEmail: true, 360 }, 361 ret: dashapi.DiscussionReminder, 362 }, 363 { 364 msg: &email.Email{ 365 Subject: "[syzbot] WARNING in abcd", 366 OwnEmail: true, 367 }, 368 ret: dashapi.DiscussionReport, 369 }, 370 { 371 msg: &email.Email{ 372 Subject: "Some human-reported bug", 373 }, 374 ret: dashapi.DiscussionMention, 375 }, 376 } 377 for _, test := range tests { 378 got := DiscussionType(test.msg) 379 if got != test.ret { 380 t.Fatalf("expected %v got %v for %v", test.ret, got, test.msg) 381 } 382 } 383 } 384 385 const dummyPatch = `diff --git a/kernel/kcov.c b/kernel/kcov.c 386 index 85e5546cd791..949ea4574412 100644 387 --- a/kernel/kcov.c 388 +++ b/kernel/kcov.c 389 @@ -127,7 +127,6 @@ void kcov_task_exit(struct task_struct *t) 390 if (kcov == NULL) 391 return; 392 - spin_lock(&kcov->lock); 393 if (WARN_ON(kcov->t != t)) { 394 ` 395 396 func TestParseSeries(t *testing.T) { 397 messages := []string{ 398 // A simple patch series. 399 `Date: Sun, 7 May 2017 19:54:00 -0700 400 Subject: [PATCH] Small patch 401 Message-ID: <First> 402 From: UserA <a@user.com> 403 Content-Type: text/plain 404 405 ` + dummyPatch, 406 // A series with a cover. 407 `Date: Sun, 7 May 2017 19:55:00 -0700 408 Subject: [PATCH net v2 00/02] A longer series 409 Message-ID: <Second> 410 From: UserB <b@user.com> 411 To: UserA <a@user.com> 412 Content-Type: text/plain 413 414 Some cover`, 415 `Date: Sun, 7 May 2017 19:56:00 -0700 416 Subject: [PATCH net v2 01/02] First patch 417 Message-ID: <Second-1> 418 From: UserC <c@user.com> 419 To: UserA <a@user.com>, UserB <b@user.com> 420 Content-Type: text/plain 421 In-Reply-To: <Second> 422 423 ` + dummyPatch, 424 `Date: Sun, 7 May 2017 19:56:00 -0700 425 Subject: [PATCH net v2 02/02] Second patch 426 Message-ID: <Second-2> 427 From: UserC <c@user.com> 428 To: UserA <a@user.com>, UserB <b@user.com> 429 Content-Type: text/plain 430 In-Reply-To: <Second> 431 432 ` + dummyPatch, 433 // Some missing patches. 434 `Date: Sun, 7 May 2017 19:57:00 -0700 435 Subject: [PATCH 01/03] Series 436 Message-ID: <Third> 437 From: Someone <a@b.com> 438 Content-Type: text/plain 439 440 ` + dummyPatch, 441 // Reply with a patch subject. 442 `Date: Sun, 7 May 2017 19:57:00 -0700 443 Subject: [PATCH] Series 444 Message-ID: <Fourth> 445 From: Someone <a@b.com> 446 Content-Type: text/plain 447 In-Reply-To: <Something> 448 449 No patch, just text`, 450 } 451 452 var emails []*Email 453 for _, m := range messages { 454 msg, err := emailFromRaw([]byte(m), nil, nil) 455 if err != nil { 456 t.Fatal(err) 457 } 458 emails = append(emails, msg) 459 } 460 461 series := PatchSeries(emails) 462 assert.Len(t, series, 4) 463 464 expectPerID := map[string]*Series{ 465 "<First>": { 466 Subject: "Small patch", 467 Version: 1, 468 Patches: []Patch{ 469 { 470 Seq: 1, 471 Email: &Email{Email: &email.Email{Subject: "[PATCH] Small patch"}}, 472 }, 473 }, 474 }, 475 "<Second>": { 476 Subject: "A longer series", 477 Version: 2, 478 Tags: []string{"net"}, 479 Patches: []Patch{ 480 { 481 Seq: 1, 482 Email: &Email{Email: &email.Email{Subject: "[PATCH v2 01/02] First patch"}}, 483 }, 484 { 485 Seq: 2, 486 Email: &Email{Email: &email.Email{Subject: "[PATCH v2 02/02] Second patch"}}, 487 }, 488 }, 489 }, 490 "<Third>": { 491 Subject: "Series", 492 Version: 1, 493 Corrupted: "the subject mentions 3 patches, 1 are found", 494 Patches: []Patch{ 495 { 496 Seq: 1, 497 Email: &Email{Email: &email.Email{Subject: "[PATCH 01/03] Series"}}, 498 }, 499 }, 500 }, 501 "<Fourth>": { 502 Subject: "Series", 503 Version: 1, 504 Corrupted: "the subject mentions 1 patches, 0 are found", 505 Patches: nil, 506 }, 507 } 508 for _, s := range series { 509 expect := expectPerID[s.MessageID] 510 if expect == nil { 511 t.Fatalf("unexpected message: %q", s.MessageID) 512 } 513 expectPerID[s.MessageID] = nil 514 t.Run(s.MessageID, func(t *testing.T) { 515 assert.Equal(t, expect.Corrupted, s.Corrupted, "corrupted differs") 516 assert.Equal(t, expect.Subject, s.Subject, "subject differs") 517 assert.Equal(t, expect.Version, s.Version, "version differs") 518 require.Len(t, s.Patches, len(expect.Patches), "patch count differs") 519 for i, expectPatch := range expect.Patches { 520 got := s.Patches[i] 521 assert.Equal(t, expectPatch.Seq, got.Seq, "seq differs") 522 } 523 }) 524 } 525 }