// 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 ( "sort" "strings" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/google/syzkaller/dashboard/dashapi" "github.com/google/syzkaller/pkg/email" ) 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{ { 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"}, }, { 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: "", }, { 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{ { 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, }, { 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: "", }, { 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{ { 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{ { 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, } emails := []*email.Email{} for _, m := range messages { msg, err := email.Parse(strings.NewReader(m), []string{"syzbot@bar.com"}, []string{}, []string{"bar.com"}) if err != nil { t.Fatal(err) } msg.Body = "" 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 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) } } }