// 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 main import ( "fmt" "reflect" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/google/syzkaller/dashboard/dashapi" "github.com/google/syzkaller/pkg/email" ) func TestDiscussionAccess(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.makeClient(clientPublic, keyPublic, true) build := testBuild(1) client.UploadBuild(build) // Bug at the first (AccesUser) stage of reporting. crash := testCrash(build, 1) client.ReportCrash(crash) rep1 := client.pollBug() // Bug at the second (AccessPublic) stage. crash2 := testCrash(build, 2) client.ReportCrash(crash2) rep2user := client.pollBug() client.updateBug(rep2user.ID, dashapi.BugStatusUpstream, "") rep2 := client.pollBug() // Patch to both bugs. firstTime := timeNow(c.ctx) c.advanceTime(time.Hour) c.expectOK(client.SaveDiscussion(&dashapi.SaveDiscussionReq{ Discussion: &dashapi.Discussion{ ID: "123", Source: dashapi.DiscussionLore, Type: dashapi.DiscussionPatch, Subject: "Patch for both bugs", BugIDs: []string{rep1.ID, rep2.ID}, Messages: []dashapi.DiscussionMessage{ { ID: "123", External: true, Time: firstTime, }, }, }, })) // Discussion about the second bug. secondTime := timeNow(c.ctx) c.advanceTime(time.Hour) c.expectOK(client.SaveDiscussion(&dashapi.SaveDiscussionReq{ Discussion: &dashapi.Discussion{ ID: "456", Source: dashapi.DiscussionLore, Type: dashapi.DiscussionReport, Subject: "Second bug reported", BugIDs: []string{rep2.ID}, Messages: []dashapi.DiscussionMessage{ { ID: "456", External: false, Time: secondTime, }, }, }, })) firstBug, _, err := findBugByReportingID(c.ctx, rep1.ID) c.expectOK(err) // Verify discussion that spans only one bug. got, err := getBugDiscussionsUI(c.ctx, firstBug) c.expectOK(err) if diff := cmp.Diff([]*uiBugDiscussion{ { Subject: "Patch for both bugs", Link: "https://lore.kernel.org/all/123/T/", Total: 1, External: 1, Last: firstTime, }, }, got); diff != "" { t.Fatal(diff) } secondBug, _, err := findBugByReportingID(c.ctx, rep2.ID) c.expectOK(err) // Verify that we also show discussions for several bugs. got, err = getBugDiscussionsUI(c.ctx, secondBug) c.expectOK(err) if diff := cmp.Diff([]*uiBugDiscussion{ { Subject: "Second bug reported", Link: "https://lore.kernel.org/all/456/T/", Total: 1, External: 0, Last: secondTime, }, { Subject: "Patch for both bugs", Link: "https://lore.kernel.org/all/123/T/", Total: 1, External: 1, Last: firstTime, }, }, got); diff != "" { t.Fatal(diff) } // Verify the summary. summary := secondBug.discussionSummary() if diff := cmp.Diff(DiscussionSummary{ AllMessages: 2, ExternalMessages: 1, LastMessage: secondTime, LastPatchMessage: firstTime, }, summary); diff != "" { t.Fatal(diff) } } func TestEmailOwnDiscussions(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.publicClient build := testBuild(1) client.UploadBuild(build) crash := testCrash(build, 1) client.ReportCrash(crash) msg := client.pollEmailBug() _, extBugID, err := email.RemoveAddrContext(msg.Sender) c.expectOK(err) // Start a discussion. incoming1 := fmt.Sprintf(`Sender: syzkaller@googlegroups.com Date: Tue, 15 Aug 2017 14:59:00 -0700 Message-ID: <1234> Subject: Bug reported From: %v To: foo@bar.com, linux-kernel@vger.kernel.org Content-Type: text/plain Hello`, msg.Sender) _, err = c.POST("/_ah/mail/lore@email.com", incoming1) c.expectOK(err) bug, _, err := findBugByReportingID(c.ctx, extBugID) c.expectOK(err) zone := time.FixedZone("", -7*60*60) got, err := getBugDiscussionsUI(c.ctx, bug) c.expectOK(err) if diff := cmp.Diff([]*uiBugDiscussion{ { Subject: "Bug reported", Link: "https://lore.kernel.org/all/1234/T/", Total: 1, External: 0, Last: time.Date(2017, time.August, 15, 14, 59, 0, 0, zone), }, }, got); diff != "" { t.Fatal(diff) } // Emulate some user-reply to the discussion. incoming2 := fmt.Sprintf(`Sender: user@user.com Date: Tue, 16 Aug 2017 14:59:00 -0700 Message-ID: <2345> Subject: Re. Bug reported From: user@user.com In-Reply-To: <1234> Cc: %v, linux-kernel@vger.kernel.org Content-Type: text/plain Hello`, msg.Sender) _, err = c.POST("/_ah/mail/lore@email.com", incoming2) c.expectOK(err) bug, _, err = findBugByReportingID(c.ctx, extBugID) c.expectOK(err) got, err = getBugDiscussionsUI(c.ctx, bug) c.expectOK(err) if diff := cmp.Diff([]*uiBugDiscussion{ { Subject: "Bug reported", Link: "https://lore.kernel.org/all/1234/T/", Total: 2, External: 1, Last: time.Date(2017, time.August, 16, 14, 59, 0, 0, zone), }, }, got); diff != "" { t.Fatal(diff) } } func TestEmailUnrelatedDiscussion(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.publicClient build := testBuild(1) client.UploadBuild(build) crash := testCrash(build, 1) client.ReportCrash(crash) msg := client.pollEmailBug() _, extBugID, err := email.RemoveAddrContext(msg.Sender) c.expectOK(err) // An email that's not sent to the target email address. incoming1 := fmt.Sprintf(`Date: Tue, 15 Aug 2017 14:59:00 -0700 Message-ID: <1234> Subject: Some discussion In-Reply-To: <2345> From: user@user.com To: %v, lore@email.com Content-Type: text/plain Hello`, msg.Sender) _, err = c.POST("/_ah/mail/"+msg.Sender, incoming1) c.expectOK(err) bug, _, err := findBugByReportingID(c.ctx, extBugID) c.expectOK(err) // The discussion should go ignored. got, err := getBugDiscussionsUI(c.ctx, bug) c.expectOK(err) if diff := cmp.Diff([]*uiBugDiscussion(nil), got); diff != "" { t.Fatal(diff) } } func TestEmailSubdiscussion(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.publicClient build := testBuild(1) client.UploadBuild(build) crash := testCrash(build, 1) client.ReportCrash(crash) msg := client.pollEmailBug() _, extBugID, err := email.RemoveAddrContext(msg.Sender) c.expectOK(err) incoming1 := fmt.Sprintf(`Date: Tue, 15 Aug 2017 14:59:00 -0700 Message-ID: <2345> Subject: Some discussion In-Reply-To: <1234> From: user@user.com To: %v Cc: lore@email.com Content-Type: text/plain Hello`, msg.Sender) _, err = c.POST("/_ah/mail/lore@email.com", incoming1) c.expectOK(err) bug, _, err := findBugByReportingID(c.ctx, extBugID) c.expectOK(err) // We have not seen the start of the discussion, but it should not go ignored. got, err := getBugDiscussionsUI(c.ctx, bug) c.expectOK(err) client.expectEQ(len(got), 1) client.expectEQ(got[0].Link, "https://lore.kernel.org/all/2345/T/") } func TestEmailPatchWithLink(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.publicClient build := testBuild(1) client.UploadBuild(build) crash := testCrash(build, 1) client.ReportCrash(crash) msg := client.pollEmailBug() _, extBugID, err := email.RemoveAddrContext(msg.Sender) c.expectOK(err) incoming1 := fmt.Sprintf(`Date: Tue, 15 Aug 2017 14:59:00 -0700 Message-ID: <2345> Subject: [PATCH v3] A lot of fixes From: user@user.com To: lore@email.com Content-Type: text/plain Hello, Link: https://testapp.appspot.com/bug?extid=%v `, extBugID) _, err = c.POST("/_ah/mail/lore@email.com", incoming1) c.expectOK(err) bug, _, err := findBugByReportingID(c.ctx, extBugID) c.expectOK(err) // We have not seen the start of the discussion, but it should not go ignored. got, err := getBugDiscussionsUI(c.ctx, bug) c.expectOK(err) client.expectEQ(len(got), 1) client.expectEQ(got[0].Link, "https://lore.kernel.org/all/2345/T/") client.expectEQ(got[0].Subject, "[PATCH v3] A lot of fixes") } func TestIgnoreBotReplies(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.publicClient build := testBuild(1) client.UploadBuild(build) crash := testCrash(build, 1) client.ReportCrash(crash) msg := client.pollEmailBug() _, extBugID, err := email.RemoveAddrContext(msg.Sender) c.expectOK(err) incoming1 := fmt.Sprintf(`Date: Tue, 15 Aug 2017 14:59:00 -0700 Message-ID: <2345> Subject: Re: Patch testing request From: %v To: lore@email.com In-Reply-To: <1234> Content-Type: text/plain Hello! `, msg.Sender) _, err = c.POST("/_ah/mail/lore@email.com", incoming1) c.expectOK(err) bug, _, err := findBugByReportingID(c.ctx, extBugID) c.expectOK(err) // We have not seen the start of the discussion, but it should not go ignored. got, err := getBugDiscussionsUI(c.ctx, bug) c.expectOK(err) client.expectEQ(len(got), 0) } func TestMessageOverflow(t *testing.T) { date := time.Date(2000, time.January, 1, 1, 0, 0, 0, time.UTC) d := &Discussion{} first, last := dashapi.DiscussionMessage{ ID: date.String(), Time: date, }, dashapi.DiscussionMessage{} d.addMessages([]dashapi.DiscussionMessage{first}) const blockSize = 100 for i := 0; i < 2*maxMessagesInDiscussion; i += blockSize { block := []dashapi.DiscussionMessage{} for j := 0; j < blockSize; j++ { date = date.Add(time.Minute) last = dashapi.DiscussionMessage{ ID: date.String(), Time: date, } block = append(block, last) } // Make sure that the first message always remains in place and the last one // is the latest one. d.addMessages(block) if !reflect.DeepEqual(first.ID, d.Messages[0].ID) { t.Fatalf("unexpected first messages") } if !reflect.DeepEqual(last.ID, d.Messages[len(d.Messages)-1].ID) { t.Fatalf("unexpected last messages") } } if len(d.Messages) != maxMessagesInDiscussion { t.Fatalf("expected len to be equal to %d, got %d", maxMessagesInDiscussion, len(d.Messages)) } }