// Copyright 2023 syzkaller project authors. All rights reserved. // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. package lore import ( "fmt" "sort" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/google/syzkaller/dashboard/dashapi" "github.com/google/syzkaller/pkg/email" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestThreadsCollection(t *testing.T) { messages := []string{ // <-- <-- . `Date: Sun, 7 May 2017 19:54:00 -0700 Subject: Thread A Message-ID: From: UserA Content-Type: text/plain Some text`, `Date: Sun, 7 May 2017 19:55:00 -0700 Subject: Re: Thread A Message-ID: From: UserB To: UserA Content-Type: text/plain In-Reply-To: Some reply`, `Date: Sun, 7 May 2017 19:56:00 -0700 Subject: Re: Re: Thread A Message-ID: From: UserC To: UserA , UserB Content-Type: text/plain In-Reply-To: Some reply (2)`, // with two children: , . `Date: Sun, 7 May 2017 19:57:00 -0700 Subject: [syzbot] Some bug Message-ID: From: syzbot Content-Type: text/plain Bug report`, `Date: Sun, 7 May 2017 19:58:00 -0700 Subject: Re: [syzbot] Some bug Message-ID: From: UserC To: syzbot In-Reply-To: Content-Type: text/plain Bug report reply`, `Date: Sun, 7 May 2017 19:58:01 -0700 Subject: Re: [syzbot] Some bug Message-ID: From: UserD To: syzbot In-Reply-To: B Content-Type: text/plain Bug report reply 2`, // And one PATCH without replies. `Date: Sun, 7 May 2017 19:58:01 -0700 Subject: [PATCH] Some bug fixed Message-ID: From: UserE Cc: syzbot Content-Type: text/plain Patch`, // An orphaned reply from a human. `Date: Sun, 7 May 2017 19:57:00 -0700 Subject: Another bug discussion In-Reply-To: Message-ID: From: person@email.com Cc: syzbot Content-Type: text/plain Bug report`, // An orphaned reply from a bot. `Date: Sun, 7 May 2017 19:57:00 -0700 Subject: Re: [syzbot] Some bug 3 In-Reply-To: Message-ID: From: syzbot+4564456@bar.com To: all@email.com Content-Type: text/plain Bug report`, } zone := time.FixedZone("", -7*60*60) expected := map[string]*Thread{ "": { Subject: "Thread A", MessageID: "", Type: dashapi.DiscussionMention, Messages: []*Email{ { Email: &email.Email{ MessageID: "", Subject: "Thread A", Date: time.Date(2017, time.May, 7, 19, 54, 0, 0, zone), Author: "a@user.com", Cc: []string{"a@user.com"}, }, }, { Email: &email.Email{ MessageID: "", Subject: "Re: Thread A", Date: time.Date(2017, time.May, 7, 19, 55, 0, 0, zone), Author: "b@user.com", Cc: []string{"a@user.com", "b@user.com"}, InReplyTo: "", }, }, { Email: &email.Email{ MessageID: "", Subject: "Re: Re: Thread A", Date: time.Date(2017, time.May, 7, 19, 56, 0, 0, zone), Author: "c@user.com", Cc: []string{"a@user.com", "b@user.com", "c@user.com"}, InReplyTo: "", }, }, }, }, "": { Subject: "[syzbot] Some bug", MessageID: "", Type: dashapi.DiscussionReport, BugIDs: []string{"4564456"}, Messages: []*Email{ { Email: &email.Email{ MessageID: "", BugIDs: []string{"4564456"}, Subject: "[syzbot] Some bug", Date: time.Date(2017, time.May, 7, 19, 57, 0, 0, zone), Author: "syzbot@bar.com", OwnEmail: true, }, }, { Email: &email.Email{ MessageID: "", BugIDs: []string{"4564456"}, Subject: "Re: [syzbot] Some bug", Date: time.Date(2017, time.May, 7, 19, 58, 0, 0, zone), Author: "c@user.com", Cc: []string{"c@user.com"}, InReplyTo: "", }, }, { Email: &email.Email{ MessageID: "", BugIDs: []string{"4564456"}, Subject: "Re: [syzbot] Some bug", Date: time.Date(2017, time.May, 7, 19, 58, 1, 0, zone), Author: "d@user.com", Cc: []string{"d@user.com"}, InReplyTo: "", }, }, }, }, "": { Subject: "[PATCH] Some bug fixed", MessageID: "", Type: dashapi.DiscussionPatch, BugIDs: []string{"12345"}, Messages: []*Email{ { Email: &email.Email{ MessageID: "", BugIDs: []string{"12345"}, Subject: "[PATCH] Some bug fixed", Date: time.Date(2017, time.May, 7, 19, 58, 1, 0, zone), Author: "e@user.com", Cc: []string{"e@user.com"}, }, }, }, }, "": { Subject: "Another bug discussion", MessageID: "", Type: dashapi.DiscussionMention, BugIDs: []string{"4564456"}, Messages: []*Email{ { Email: &email.Email{ MessageID: "", InReplyTo: "", Date: time.Date(2017, time.May, 7, 19, 57, 0, 0, zone), BugIDs: []string{"4564456"}, Cc: []string{"person@email.com"}, Subject: "Another bug discussion", Author: "person@email.com", }, }, }, }, "": nil, } var emails []*Email for _, m := range messages { msg, err := emailFromRaw([]byte(m), []string{"syzbot@bar.com"}, []string{"bar.com"}) if err != nil { t.Fatal(err) } msg.RawCc = nil emails = append(emails, msg) } threads := Threads(emails) got := map[string]*Thread{} for _, d := range threads { sort.Slice(d.Messages, func(i, j int) bool { return d.Messages[i].Date.Before(d.Messages[j].Date) }) got[d.MessageID] = d } for key, val := range expected { if diff := cmp.Diff(val, got[key]); diff != "" { t.Fatalf("%s: %s", key, diff) } } if len(threads) > len(expected) { t.Fatalf("expected %d threads, got %d", len(expected), len(threads)) } } func TestParsePatchSubject(t *testing.T) { tests := []struct { subj string ret PatchSubject }{ { subj: `[PATCH] abcd`, ret: PatchSubject{Title: "abcd"}, }, { subj: `[PATCH 00/20] abcd`, ret: PatchSubject{Title: "abcd", Seq: value[int](0), Total: value[int](20)}, }, { subj: `[PATCH 5/6] abcd`, ret: PatchSubject{Title: "abcd", Seq: value[int](5), Total: value[int](6)}, }, { subj: `[PATCH RFC v3 0/4] abcd`, ret: PatchSubject{ Title: "abcd", Tags: []string{"RFC"}, Version: value[int](3), Seq: value[int](0), Total: value[int](4), }, }, { subj: `[RFC PATCH] abcd`, ret: PatchSubject{Title: "abcd", Tags: []string{"RFC"}}, }, { subj: `[PATCH net-next v2 00/21] abcd`, ret: PatchSubject{ Title: "abcd", Tags: []string{"net-next"}, Version: value[int](2), Seq: value[int](0), Total: value[int](21), }, }, { subj: `[PATCH v2 RESEND] abcd`, ret: PatchSubject{Title: "abcd", Version: value[int](2), Tags: []string{"RESEND"}}, }, { subj: `[PATCH RFC net-next v3 05/21] abcd`, ret: PatchSubject{ Title: "abcd", Tags: []string{"RFC", "net-next"}, Version: value[int](3), Seq: value[int](5), Total: value[int](21), }, }, } for id, test := range tests { t.Run(fmt.Sprint(id), func(t *testing.T) { ret, ok := parsePatchSubject(test.subj) assert.True(t, ok) assert.Equal(t, test.ret, ret) }) } } func TestDiscussionType(t *testing.T) { tests := []struct { msg *email.Email ret dashapi.DiscussionType }{ { msg: &email.Email{ Subject: "[PATCH] Bla-bla", }, ret: dashapi.DiscussionPatch, }, { msg: &email.Email{ Subject: "[patch v3] Bla-bla", }, ret: dashapi.DiscussionPatch, }, { msg: &email.Email{ Subject: "[RFC PATCH] Bla-bla", }, ret: dashapi.DiscussionPatch, }, { msg: &email.Email{ Subject: "[RESEND PATCH] Bla-bla", }, ret: dashapi.DiscussionPatch, }, { msg: &email.Email{ Subject: "[syzbot] Monthly ext4 report", OwnEmail: true, }, ret: dashapi.DiscussionReminder, }, { msg: &email.Email{ Subject: "[syzbot] WARNING in abcd", OwnEmail: true, }, ret: dashapi.DiscussionReport, }, { msg: &email.Email{ Subject: "Some human-reported bug", }, ret: dashapi.DiscussionMention, }, } for _, test := range tests { got := DiscussionType(test.msg) if got != test.ret { t.Fatalf("expected %v got %v for %v", test.ret, got, test.msg) } } } const dummyPatch = `diff --git a/kernel/kcov.c b/kernel/kcov.c index 85e5546cd791..949ea4574412 100644 --- a/kernel/kcov.c +++ b/kernel/kcov.c @@ -127,7 +127,6 @@ void kcov_task_exit(struct task_struct *t) if (kcov == NULL) return; - spin_lock(&kcov->lock); if (WARN_ON(kcov->t != t)) { ` func TestParseSeries(t *testing.T) { messages := []string{ // A simple patch series. `Date: Sun, 7 May 2017 19:54:00 -0700 Subject: [PATCH] Small patch Message-ID: From: UserA Content-Type: text/plain ` + dummyPatch, // A series with a cover. `Date: Sun, 7 May 2017 19:55:00 -0700 Subject: [PATCH net v2 00/02] A longer series Message-ID: From: UserB To: UserA Content-Type: text/plain Some cover`, `Date: Sun, 7 May 2017 19:56:00 -0700 Subject: [PATCH net v2 01/02] First patch Message-ID: From: UserC To: UserA , UserB Content-Type: text/plain In-Reply-To: ` + dummyPatch, `Date: Sun, 7 May 2017 19:56:00 -0700 Subject: [PATCH net v2 02/02] Second patch Message-ID: From: UserC To: UserA , UserB Content-Type: text/plain In-Reply-To: ` + dummyPatch, // Some missing patches. `Date: Sun, 7 May 2017 19:57:00 -0700 Subject: [PATCH 01/03] Series Message-ID: From: Someone Content-Type: text/plain ` + dummyPatch, // Reply with a patch subject. `Date: Sun, 7 May 2017 19:57:00 -0700 Subject: [PATCH] Series Message-ID: From: Someone Content-Type: text/plain In-Reply-To: No patch, just text`, } var emails []*Email for _, m := range messages { msg, err := emailFromRaw([]byte(m), nil, nil) if err != nil { t.Fatal(err) } emails = append(emails, msg) } series := PatchSeries(emails) assert.Len(t, series, 4) expectPerID := map[string]*Series{ "": { Subject: "Small patch", Version: 1, Patches: []Patch{ { Seq: 1, Email: &Email{Email: &email.Email{Subject: "[PATCH] Small patch"}}, }, }, }, "": { Subject: "A longer series", Version: 2, Tags: []string{"net"}, Patches: []Patch{ { Seq: 1, Email: &Email{Email: &email.Email{Subject: "[PATCH v2 01/02] First patch"}}, }, { Seq: 2, Email: &Email{Email: &email.Email{Subject: "[PATCH v2 02/02] Second patch"}}, }, }, }, "": { Subject: "Series", Version: 1, Corrupted: "the subject mentions 3 patches, 1 are found", Patches: []Patch{ { Seq: 1, Email: &Email{Email: &email.Email{Subject: "[PATCH 01/03] Series"}}, }, }, }, "": { Subject: "Series", Version: 1, Corrupted: "the subject mentions 1 patches, 0 are found", Patches: nil, }, } for _, s := range series { expect := expectPerID[s.MessageID] if expect == nil { t.Fatalf("unexpected message: %q", s.MessageID) } expectPerID[s.MessageID] = nil t.Run(s.MessageID, func(t *testing.T) { assert.Equal(t, expect.Corrupted, s.Corrupted, "corrupted differs") assert.Equal(t, expect.Subject, s.Subject, "subject differs") assert.Equal(t, expect.Version, s.Version, "version differs") require.Len(t, s.Patches, len(expect.Patches), "patch count differs") for i, expectPatch := range expect.Patches { got := s.Patches[i] assert.Equal(t, expectPatch.Seq, got.Seq, "seq differs") } }) } }