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 }