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  }