github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/syz-cluster/email-reporter/stream_test.go (about)

     1  // Copyright 2025 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  	"context"
     8  	"os"
     9  	"path/filepath"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/google/syzkaller/pkg/email/lore"
    14  	"github.com/google/syzkaller/pkg/vcs"
    15  	"github.com/google/syzkaller/syz-cluster/pkg/api"
    16  	"github.com/google/syzkaller/syz-cluster/pkg/app"
    17  	"github.com/google/syzkaller/syz-cluster/pkg/controller"
    18  	"github.com/stretchr/testify/assert"
    19  )
    20  
    21  func TestEmailStream(t *testing.T) {
    22  	env, ctx := app.TestEnvironment(t)
    23  	testSeries := controller.DummySeries()
    24  	handler, reporterClient, _ := setupHandlerTest(t, env, ctx, testSeries)
    25  	report, err := handler.PollAndReport(ctx)
    26  	assert.NoError(t, err)
    27  
    28  	// Simulate our reply.
    29  	err = reporterClient.ConfirmReport(ctx, report.ID)
    30  	assert.NoError(t, err)
    31  	const messageID = "<message-id>"
    32  	_, err = reporterClient.RecordReply(ctx, &api.RecordReplyReq{
    33  		MessageID: messageID,
    34  		ReportID:  report.ID,
    35  		Reporter:  api.LKMLReporter,
    36  	})
    37  	assert.NoError(t, err)
    38  
    39  	// Emulate the lore archive and set up the loop.
    40  	loreArchive := newLoreArchive(t)
    41  	writeTo := make(chan *lore.Email, 16)
    42  	emailCfg := &app.EmailConfig{
    43  		LoreArchiveURL: loreArchive.remoteRef(),
    44  		SMTP: &app.SMTPConfig{
    45  			From: `syzbot@syzkaller.appspotmail.com`,
    46  		},
    47  		Dashapi: &app.DashapiConfig{
    48  			From:          "bot@syzbot.org",
    49  			ContextPrefix: "ci_",
    50  		},
    51  	}
    52  	stream := NewLKMLEmailStream(t.TempDir(), reporterClient, emailCfg, writeTo)
    53  	cancel := startStreamLoop(t, ctx, stream)
    54  
    55  	t.Logf("sending a direct reply")
    56  	loreArchive.saveMessage(t, `Date: Sun, 7 May 2017 19:54:00 -0700
    57  Subject: Reply to the Report
    58  Message-ID: <direct-reply>
    59  In-Reply-To: `+messageID+`
    60  From: Someone <a@syzbot.org>
    61  Content-Type: text/plain
    62  
    63  `)
    64  	msg := <-writeTo
    65  	assert.Equal(t, "<direct-reply>", msg.MessageID)
    66  	assert.Equal(t, []string{report.ID}, msg.BugIDs)
    67  
    68  	t.Logf("sending an indirect reply")
    69  	loreArchive.saveMessage(t, `Date: Sun, 7 May 2017 19:55:00 -0700
    70  Subject: Reply to the Reply
    71  Message-ID: <indirect-reply>
    72  In-Reply-To: <direct-reply>
    73  From: Someone Else <b@syzbot.org>
    74  Content-Type: text/plain
    75  
    76  `)
    77  	msg = <-writeTo
    78  	assert.Equal(t, []string{report.ID}, msg.BugIDs)
    79  
    80  	t.Logf("sending an unrelated message")
    81  	loreArchive.saveMessage(t, `Date: Sun, 7 May 2017 19:56:00 -0700
    82  Subject: Reply to the Reply
    83  Message-ID: <another-reply>
    84  From: Someone Else <b@syzbot.org>
    85  Content-Type: text/plain
    86  
    87  `)
    88  	msg = <-writeTo
    89  	assert.Len(t, msg.BugIDs, 0)
    90  
    91  	t.Logf("identify by email context")
    92  	loreArchive.saveMessage(t, `Date: Sun, 7 May 2017 19:55:00 -0700
    93  Subject: New thread
    94  Message-ID: <new-thread>
    95  In-Reply-To: <whatever>
    96  From: Someone Else <b@syzbot.org>
    97  Cc: <bot+ci_`+report.ID+`@syzbot.org>
    98  Content-Type: text/plain
    99  
   100  `)
   101  	msg = <-writeTo
   102  	assert.Equal(t, []string{report.ID}, msg.BugIDs)
   103  
   104  	t.Logf("own email (SMTP)")
   105  	loreArchive.saveMessage(t, `Date: Sun, 7 May 2017 19:55:00 -0700
   106  Subject: New thread
   107  Message-ID: <new-thread>
   108  In-Reply-To: <whatever>
   109  From: Ourselves <`+emailCfg.SMTP.From+`>
   110  Content-Type: text/plain
   111  
   112  `)
   113  	msg = <-writeTo
   114  	assert.True(t, msg.OwnEmail)
   115  
   116  	t.Logf("own email (dashapi)")
   117  	loreArchive.saveMessage(t, `Date: Sun, 7 May 2017 19:55:00 -0700
   118  Subject: New thread
   119  Message-ID: <new-thread>
   120  In-Reply-To: <whatever>
   121  From: Ourselves <`+emailCfg.Dashapi.From+`>
   122  Content-Type: text/plain
   123  
   124  `)
   125  	msg = <-writeTo
   126  	assert.True(t, msg.OwnEmail)
   127  
   128  	t.Logf("stopping the loop")
   129  	cancel()
   130  
   131  	// Emulate service restart.
   132  	stream = NewLKMLEmailStream(t.TempDir(), reporterClient, emailCfg, writeTo)
   133  	cancel = startStreamLoop(t, ctx, stream)
   134  	defer cancel()
   135  	// Only the unrelated message is expected to pop up.
   136  	msg = <-writeTo
   137  	assert.Equal(t, "<another-reply>", msg.MessageID)
   138  }
   139  
   140  func startStreamLoop(t *testing.T, ctx context.Context, stream *LKMLEmailStream) func() {
   141  	done := make(chan struct{})
   142  	loopCtx, cancel := context.WithCancel(ctx)
   143  	go func() {
   144  		err := stream.Loop(loopCtx, time.Second/10)
   145  		assert.NoError(t, err)
   146  		close(done)
   147  	}()
   148  	return func() {
   149  		cancel()
   150  		<-done
   151  	}
   152  }
   153  
   154  type loreArchive struct {
   155  	repo *vcs.TestRepo
   156  }
   157  
   158  func newLoreArchive(t *testing.T) *loreArchive {
   159  	return &loreArchive{
   160  		repo: vcs.MakeTestRepo(t, t.TempDir()),
   161  	}
   162  }
   163  
   164  func (a *loreArchive) remoteRef() string {
   165  	return a.repo.Dir
   166  }
   167  
   168  func (a *loreArchive) saveMessage(t *testing.T, raw string) {
   169  	err := os.WriteFile(filepath.Join(a.repo.Dir, "m"), []byte(raw), 0666)
   170  	assert.NoError(t, err)
   171  	a.repo.Git("add", "m")
   172  	a.repo.CommitChange("some title")
   173  }