aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/manager/diff
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2026-01-25 21:01:16 +0000
committerAleksandr Nogikh <nogikh@google.com>2026-01-26 15:43:37 +0000
commit30c4bb8427fe0d12bfff0fea81bf31d04910129e (patch)
tree6ff01bffdf8f23a3e76a1218a4b4af235e137f29 /pkg/manager/diff
parentdd170fcec9a15df2760e4cd84d22aab51ef0d172 (diff)
pkg/manager/diff: add tests
Refactor the package to support testing and add a number of basic tests.
Diffstat (limited to 'pkg/manager/diff')
-rw-r--r--pkg/manager/diff/diff_test.go223
-rw-r--r--pkg/manager/diff/kernel.go28
-rw-r--r--pkg/manager/diff/manager.go71
-rw-r--r--pkg/manager/diff/manager_test.go188
-rw-r--r--pkg/manager/diff/repro.go18
5 files changed, 497 insertions, 31 deletions
diff --git a/pkg/manager/diff/diff_test.go b/pkg/manager/diff/diff_test.go
new file mode 100644
index 000000000..3ac2cbfcb
--- /dev/null
+++ b/pkg/manager/diff/diff_test.go
@@ -0,0 +1,223 @@
+// Copyright 2026 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 diff
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "github.com/google/syzkaller/pkg/flatrpc"
+ "github.com/google/syzkaller/pkg/manager"
+ "github.com/google/syzkaller/pkg/mgrconfig"
+ "github.com/google/syzkaller/pkg/report"
+ "github.com/google/syzkaller/pkg/repro"
+ "github.com/google/syzkaller/prog"
+ _ "github.com/google/syzkaller/prog/test"
+ "github.com/google/syzkaller/vm"
+ "github.com/google/syzkaller/vm/dispatcher"
+)
+
+type testEnv struct {
+ t *testing.T
+ ctx context.Context
+ cancel context.CancelFunc
+ diffCtx *diffContext
+ base *MockKernel
+ new *MockKernel
+ done chan error
+}
+
+func newTestEnv(t *testing.T, cfg *Config) *testEnv {
+ if cfg == nil {
+ cfg = &Config{}
+ }
+ if cfg.Store == nil {
+ cfg.Store = &manager.DiffFuzzerStore{BasePath: t.TempDir()}
+ }
+ if cfg.PatchedOnly == nil {
+ cfg.PatchedOnly = make(chan *Bug, 1)
+ }
+ if cfg.BaseCrashes == nil {
+ cfg.BaseCrashes = make(chan string, 1)
+ }
+ if cfg.runner == nil {
+ // Default to a no-op runner if none provided.
+ cfg.runner = newMockRunner(nil)
+ }
+
+ diffCtx := &diffContext{
+ cfg: *cfg,
+ doneRepro: make(chan *manager.ReproResult, 1),
+ store: cfg.Store,
+ reproAttempts: map[string]int{},
+ patchedOnly: cfg.PatchedOnly,
+ }
+
+ base := &MockKernel{
+ CrashesCh: make(chan *report.Report, 1),
+ LoopFunc: func(ctx context.Context) error { return nil },
+ }
+ newKernel := &MockKernel{
+ CrashesCh: make(chan *report.Report, 1),
+ LoopFunc: func(ctx context.Context) error { return nil },
+ }
+
+ newKernel.PoolVal = dispatcher.NewPool[*vm.Instance](1, func(ctx context.Context, index int) (*vm.Instance, error) {
+ return &vm.Instance{}, nil
+ }, func(ctx context.Context, inst *vm.Instance, upd dispatcher.UpdateInfo) {
+ })
+ newKernel.ConfigVal = &mgrconfig.Config{}
+
+ diffCtx.base = base
+ diffCtx.new = newKernel
+
+ ctx, cancel := context.WithCancel(context.Background())
+
+ return &testEnv{
+ t: t,
+ ctx: ctx,
+ cancel: cancel,
+ diffCtx: diffCtx,
+ base: base,
+ new: newKernel,
+ done: make(chan error, 1),
+ }
+}
+
+func (env *testEnv) start() {
+ go func() {
+ env.done <- env.diffCtx.Loop(env.ctx)
+ }()
+}
+
+func (env *testEnv) close() {
+ env.cancel()
+ select {
+ case <-env.done:
+ case <-time.After(5 * time.Second):
+ env.t.Error("timeout waiting for diffCtx loop to exit")
+ }
+}
+
+func (env *testEnv) waitForStatus(title string, status manager.DiffBugStatus) {
+ env.t.Helper()
+ start := time.Now()
+ for time.Since(start) < 15*time.Second {
+ for _, bug := range env.diffCtx.store.List() {
+ if bug.Title == title && bug.Status == status {
+ return
+ }
+ }
+ time.Sleep(10 * time.Millisecond)
+ }
+ env.t.Fatalf("timed out waiting for status: %s", status)
+}
+
+type MockKernel struct {
+ LoopFunc func(ctx context.Context) error
+ CrashesCh chan *report.Report
+ TriageProgressVal float64
+ ProgsPerAreaVal map[string]int
+ CoverFiltersVal manager.CoverageFilters
+ ConfigVal *mgrconfig.Config
+ PoolVal *vm.Dispatcher
+ FeaturesVal flatrpc.Feature
+ ReporterVal *report.Reporter
+}
+
+func (mk *MockKernel) Loop(ctx context.Context) error {
+ if mk.LoopFunc != nil {
+ return mk.LoopFunc(ctx)
+ }
+ <-ctx.Done()
+ return nil
+}
+
+func (mk *MockKernel) Crashes() <-chan *report.Report {
+ return mk.CrashesCh
+}
+
+func (mk *MockKernel) TriageProgress() float64 {
+ return mk.TriageProgressVal
+}
+
+func (mk *MockKernel) ProgsPerArea() map[string]int {
+ return mk.ProgsPerAreaVal
+}
+
+func (mk *MockKernel) CoverFilters() manager.CoverageFilters {
+ return mk.CoverFiltersVal
+}
+
+func (mk *MockKernel) Config() *mgrconfig.Config {
+ return mk.ConfigVal
+}
+
+func (mk *MockKernel) Pool() *vm.Dispatcher {
+ return mk.PoolVal
+}
+
+func (mk *MockKernel) Features() flatrpc.Feature {
+ return mk.FeaturesVal
+}
+
+func (mk *MockKernel) Reporter() *report.Reporter {
+ return mk.ReporterVal
+}
+
+func (mk *MockKernel) FinishCorpusTriage() {
+ mk.TriageProgressVal = 1.0
+}
+
+type mockRunner struct {
+ runFunc func(ctx context.Context, k Kernel, r *repro.Result, fullRepro bool) *reproRunnerResult
+ doneCh chan reproRunnerResult
+}
+
+func newMockRunner(cb func(context.Context, Kernel, *repro.Result, bool) *reproRunnerResult) *mockRunner {
+ return &mockRunner{
+ runFunc: cb,
+ doneCh: make(chan reproRunnerResult, 1),
+ }
+}
+
+func (m *mockRunner) Run(ctx context.Context, k Kernel, r *repro.Result, fullRepro bool) {
+ if m.runFunc != nil {
+ res := m.runFunc(ctx, k, r, fullRepro)
+ if res != nil {
+ m.doneCh <- *res
+ }
+ }
+}
+
+func (m *mockRunner) Results() <-chan reproRunnerResult {
+ return m.doneCh
+}
+
+func mockRepro(title string, err error) func(context.Context, []byte, repro.Environment) (
+ *repro.Result, *repro.Stats, error) {
+ return mockReproCallback(title, err, nil)
+}
+
+func mockReproCallback(title string, returnErr error,
+ callback func()) func(context.Context, []byte, repro.Environment) (
+ *repro.Result, *repro.Stats, error) {
+ return func(ctx context.Context, crashLog []byte, env repro.Environment) (*repro.Result, *repro.Stats, error) {
+ if callback != nil {
+ callback()
+ }
+ if returnErr != nil {
+ return nil, nil, returnErr
+ }
+ target, err := prog.GetTarget("test", "64")
+ if err != nil {
+ return nil, nil, err
+ }
+ return &repro.Result{
+ Report: &report.Report{Title: title},
+ Prog: target.DataMmapProg(),
+ }, &repro.Stats{}, nil
+ }
+}
diff --git a/pkg/manager/diff/kernel.go b/pkg/manager/diff/kernel.go
index 75bbe6e42..9bcf01bf9 100644
--- a/pkg/manager/diff/kernel.go
+++ b/pkg/manager/diff/kernel.go
@@ -297,7 +297,7 @@ func (kc *kernelContext) runInstance(ctx context.Context, inst *vm.Instance,
return nil, err
}
-func (kc *kernelContext) triageProgress() float64 {
+func (kc *kernelContext) TriageProgress() float64 {
fuzzer := kc.fuzzer.Load()
if fuzzer == nil {
return 0
@@ -310,10 +310,34 @@ func (kc *kernelContext) triageProgress() float64 {
return 1.0 - float64(fuzzer.CandidatesToTriage())/float64(total)
}
-func (kc *kernelContext) progsPerArea() map[string]int {
+func (kc *kernelContext) ProgsPerArea() map[string]int {
fuzzer := kc.fuzzer.Load()
if fuzzer == nil {
return nil
}
return fuzzer.Config.Corpus.ProgsPerArea()
}
+
+func (kc *kernelContext) Crashes() <-chan *report.Report {
+ return kc.crashes
+}
+
+func (kc *kernelContext) CoverFilters() manager.CoverageFilters {
+ return kc.coverFilters
+}
+
+func (kc *kernelContext) Config() *mgrconfig.Config {
+ return kc.cfg
+}
+
+func (kc *kernelContext) Pool() *vm.Dispatcher {
+ return kc.pool
+}
+
+func (kc *kernelContext) Features() flatrpc.Feature {
+ return kc.features
+}
+
+func (kc *kernelContext) Reporter() *report.Reporter {
+ return kc.reporter
+}
diff --git a/pkg/manager/diff/manager.go b/pkg/manager/diff/manager.go
index cb19add63..ca6968ea1 100644
--- a/pkg/manager/diff/manager.go
+++ b/pkg/manager/diff/manager.go
@@ -13,6 +13,7 @@ import (
"sync"
"time"
+ "github.com/google/syzkaller/pkg/flatrpc"
"github.com/google/syzkaller/pkg/fuzzer/queue"
"github.com/google/syzkaller/pkg/log"
"github.com/google/syzkaller/pkg/manager"
@@ -44,6 +45,9 @@ type Config struct {
// the particular base kernel has already been seen to crash with the given title.
// It helps reduce the number of unnecessary reproductions.
IgnoreCrash func(context.Context, string) (bool, error)
+
+ runner runner
+ runRepro func(context.Context, []byte, repro.Environment) (*repro.Result, *repro.Stats, error)
}
func (cfg *Config) TriageDeadline() <-chan time.Time {
@@ -88,6 +92,13 @@ func Run(ctx context.Context, baseCfg, newCfg *mgrconfig.Config, cfg Config) err
base.source = stream
new.duplicateInto = stream
+ if cfg.runRepro == nil {
+ cfg.runRepro = repro.Run
+ }
+ if cfg.runner == nil {
+ cfg.runner = &reproRunner{done: make(chan reproRunnerResult, 2)}
+ }
+
diffCtx := &diffContext{
cfg: cfg,
doneRepro: make(chan *manager.ReproResult),
@@ -115,14 +126,26 @@ func Run(ctx context.Context, baseCfg, newCfg *mgrconfig.Config, cfg Config) err
return eg.Wait()
}
+type Kernel interface {
+ Loop(ctx context.Context) error
+ Crashes() <-chan *report.Report
+ TriageProgress() float64
+ ProgsPerArea() map[string]int
+ CoverFilters() manager.CoverageFilters
+ Config() *mgrconfig.Config
+ Pool() *vm.Dispatcher
+ Features() flatrpc.Feature
+ Reporter() *report.Reporter
+}
+
type diffContext struct {
cfg Config
store *manager.DiffFuzzerStore
http *manager.HTTPServer
doneRepro chan *manager.ReproResult
- base *kernelContext
- new *kernelContext
+ base Kernel
+ new Kernel
patchedOnly chan *Bug
mu sync.Mutex
@@ -138,7 +161,7 @@ const (
func (dc *diffContext) Loop(ctx context.Context) error {
g, groupCtx := errgroup.WithContext(ctx)
- reproLoop := manager.NewReproLoop(dc, dc.new.pool.Total()-dc.new.cfg.FuzzingVMs, false)
+ reproLoop := manager.NewReproLoop(dc, dc.new.Pool().Total()-dc.new.Config().FuzzingVMs, false)
if dc.http != nil {
dc.http.ReproLoop = reproLoop
g.Go(func() error {
@@ -163,7 +186,7 @@ func (dc *diffContext) Loop(ctx context.Context) error {
g.Go(func() error { return dc.base.Loop(groupCtx) })
g.Go(func() error { return dc.new.Loop(groupCtx) })
- runner := &reproRunner{done: make(chan reproRunnerResult, 2), kernel: dc.base}
+ runner := dc.cfg.runner
statTimer := time.NewTicker(5 * time.Minute)
loop:
for {
@@ -177,10 +200,10 @@ loop:
}
data, _ := json.MarshalIndent(vals, "", " ")
log.Logf(0, "STAT %s", data)
- case rep := <-dc.base.crashes:
+ case rep := <-dc.base.Crashes():
log.Logf(1, "base crash: %v", rep.Title)
dc.reportBaseCrash(groupCtx, rep)
- case ret := <-runner.done:
+ case ret := <-runner.Results():
dc.handleReproResult(groupCtx, ret, reproLoop)
case ret := <-dc.doneRepro:
// We have finished reproducing a crash from the patched instance.
@@ -195,7 +218,7 @@ loop:
dc.store.UpdateStatus(ret.Repro.Report.Title, manager.DiffBugStatusVerifying)
g.Go(func() error {
- runner.Run(groupCtx, ret.Repro, ret.Crash.FullRepro)
+ runner.Run(groupCtx, dc.base, ret.Repro, ret.Crash.FullRepro)
return nil
})
} else {
@@ -204,7 +227,7 @@ loop:
dc.store.UpdateStatus(origTitle, manager.DiffBugStatusCompleted)
}
dc.store.SaveRepro(ret)
- case rep := <-dc.new.crashes:
+ case rep := <-dc.new.Crashes():
// A new crash is found on the patched instance.
crash := &manager.Crash{Report: rep}
need := dc.NeedRepro(crash)
@@ -241,9 +264,9 @@ func (dc *diffContext) handleReproResult(ctx context.Context, ret reproRunnerRes
Repro: ret.repro,
}:
}
- log.Logf(0, "patched-only: %s", ret.reproReport.Title)
// Now that we know this bug only affects the patch kernel, we can spend more time
// generating a minimalistic repro and a C repro.
+ log.Logf(0, "patched-only: %s", ret.reproReport.Title)
if !ret.fullRepro {
reproLoop.Enqueue(&manager.Crash{
Report: &report.Report{
@@ -290,21 +313,21 @@ func (dc *diffContext) reportBaseCrash(ctx context.Context, rep *report.Report)
}
func (dc *diffContext) waitCorpusTriage(ctx context.Context, threshold float64) chan struct{} {
- const backOffTime = 30 * time.Second
+ const triageCheckPeriod = 30 * time.Second
ret := make(chan struct{})
go func() {
for {
- select {
- case <-time.After(backOffTime):
- case <-ctx.Done():
- return
- }
- triaged := dc.new.triageProgress()
+ triaged := dc.new.TriageProgress()
if triaged >= threshold {
log.Logf(0, "triaged %.1f%% of the corpus", triaged*100.0)
close(ret)
return
}
+ select {
+ case <-time.After(triageCheckPeriod):
+ case <-ctx.Done():
+ return
+ }
}
}()
return ret
@@ -328,7 +351,7 @@ func (dc *diffContext) monitorPatchedCoverage(ctx context.Context) error {
// By this moment, we must have coverage filters already filled out.
focusPCs := 0
// The last one is "everything else", so it's not of interest.
- coverFilters := dc.new.coverFilters
+ coverFilters := dc.new.CoverFilters()
for i := 0; i < len(coverFilters.Areas)-1; i++ {
focusPCs += len(coverFilters.Areas[i].CoverPCs)
}
@@ -344,7 +367,7 @@ func (dc *diffContext) monitorPatchedCoverage(ctx context.Context) error {
case <-ctx.Done():
return nil
}
- focusAreaStats := dc.new.progsPerArea()
+ focusAreaStats := dc.new.ProgsPerArea()
if focusAreaStats[symbolsArea]+focusAreaStats[filesArea]+focusAreaStats[includesArea] > 0 {
log.Logf(0, "fuzzer has reached the modified code (%d + %d + %d), continuing fuzzing",
focusAreaStats[symbolsArea], focusAreaStats[filesArea], focusAreaStats[includesArea])
@@ -391,11 +414,11 @@ func (dc *diffContext) RunRepro(ctx context.Context, crash *manager.Crash) *mana
dc.reproAttempts[crash.Title]++
dc.mu.Unlock()
- res, stats, err := repro.Run(ctx, crash.Output, repro.Environment{
- Config: dc.new.cfg,
- Features: dc.new.features,
- Reporter: dc.new.reporter,
- Pool: dc.new.pool,
+ res, stats, err := dc.cfg.runRepro(ctx, crash.Output, repro.Environment{
+ Config: dc.new.Config(),
+ Features: dc.new.Features(),
+ Reporter: dc.new.Reporter(),
+ Pool: dc.new.Pool(),
Fast: !crash.FullRepro,
})
if res != nil && res.Report != nil {
@@ -419,5 +442,5 @@ func (dc *diffContext) RunRepro(ctx context.Context, crash *manager.Crash) *mana
}
func (dc *diffContext) ResizeReproPool(size int) {
- dc.new.pool.ReserveForRun(size)
+ dc.new.Pool().ReserveForRun(size)
}
diff --git a/pkg/manager/diff/manager_test.go b/pkg/manager/diff/manager_test.go
index 7769fc8fd..89a35c794 100644
--- a/pkg/manager/diff/manager_test.go
+++ b/pkg/manager/diff/manager_test.go
@@ -4,11 +4,20 @@
package diff
import (
+ "context"
+ "errors"
"testing"
+ "time"
+ "github.com/google/syzkaller/pkg/manager"
+ "github.com/google/syzkaller/pkg/report"
+ "github.com/google/syzkaller/pkg/repro"
+ _ "github.com/google/syzkaller/prog/test"
"github.com/stretchr/testify/assert"
)
+const testTimeout = 15 * time.Second
+
func TestNeedReproForTitle(t *testing.T) {
for title, skip := range map[string]bool{
"no output from test machine": false,
@@ -21,3 +30,182 @@ func TestNeedReproForTitle(t *testing.T) {
assert.Equal(t, skip, needReproForTitle(title), "title=%q", title)
}
}
+
+func TestDiffBaseCrashInterception(t *testing.T) {
+ env := newTestEnv(t, nil)
+ defer env.close()
+ env.start()
+
+ env.base.CrashesCh <- &report.Report{Title: "base_crash"}
+
+ select {
+ case title := <-env.diffCtx.cfg.BaseCrashes:
+ assert.Equal(t, "base_crash", title)
+ case <-time.After(testTimeout):
+ t.Error("expected base crash")
+ }
+}
+
+func TestDiffExternalIgnore(t *testing.T) {
+ // Config ignores crash -> Patched crash ignored -> No repro.
+ runReproCalled := false
+ mockRunRepro := mockReproCallback("important_crash", nil, func() {
+ runReproCalled = true
+ })
+
+ env := newTestEnv(t, &Config{
+ IgnoreCrash: func(ctx context.Context, title string) (bool, error) {
+ if title == "ignored_crash" {
+ return true, nil
+ }
+ return false, nil
+ },
+ runRepro: mockRunRepro,
+ })
+ defer env.close()
+
+ env.new.FinishCorpusTriage()
+ env.start()
+
+ env.new.CrashesCh <- &report.Report{Title: "ignored_crash", Report: []byte("log")}
+ env.waitForStatus("ignored_crash", manager.DiffBugStatusIgnored)
+ assert.False(t, runReproCalled, "should not repro ignored crash")
+
+ env.new.CrashesCh <- &report.Report{Title: "important_crash", Report: []byte("log")}
+ env.waitForStatus("important_crash", manager.DiffBugStatusVerifying)
+}
+
+func TestDiffSuccess(t *testing.T) {
+ // Patched kernel crashes -> Repro succeeds -> Base kernel does NOT crash -> PatchedOnly reported.
+
+ // Mock Runner (Repro on Base).
+ env := newTestEnv(t, &Config{
+ runRepro: mockRepro("crash_title", nil),
+ runner: newMockRunner(func(ctx context.Context, k Kernel, r *repro.Result, fullRepro bool) *reproRunnerResult {
+ // Simulate successful run on base without crash.
+ return &reproRunnerResult{
+ reproReport: r.Report,
+ repro: r,
+ crashReport: nil, // No crash on base.
+ fullRepro: true,
+ }
+ }),
+ })
+ defer env.close()
+
+ env.new.FinishCorpusTriage()
+ env.start()
+
+ env.new.CrashesCh <- &report.Report{Title: "crash_title", Report: []byte("log")}
+ select {
+ case bug := <-env.diffCtx.patchedOnly:
+ assert.Equal(t, "crash_title", bug.Report.Title)
+ case <-time.After(testTimeout):
+ t.Fatal("expected patched only report")
+ }
+}
+
+func TestDiffFailNoRepro(t *testing.T) {
+ // Patched kernel crashes -> Repro fails -> No report.
+ env := newTestEnv(t, &Config{
+ runRepro: mockRepro("", errors.New("repro failed")),
+ })
+ defer env.close()
+
+ env.new.FinishCorpusTriage()
+ env.start()
+
+ env.new.CrashesCh <- &report.Report{Title: "crash_title", Report: []byte("log")}
+ env.waitForStatus("crash_title", manager.DiffBugStatusCompleted)
+}
+
+func TestDiffFailBaseCrash(t *testing.T) {
+ // Patched kernel crashes -> Repro succeeds -> Base also crashes -> No PatchedOnly report.
+ env := newTestEnv(t, &Config{
+ runRepro: mockRepro("crash_title", nil),
+ runner: newMockRunner(func(ctx context.Context, k Kernel, r *repro.Result, fullRepro bool) *reproRunnerResult {
+ return &reproRunnerResult{
+ reproReport: r.Report,
+ repro: r,
+ crashReport: &report.Report{Title: "crash_title"}, // Base crashed.
+ }
+ }),
+ })
+ defer env.close()
+
+ env.new.FinishCorpusTriage()
+ env.start()
+
+ env.new.CrashesCh <- &report.Report{Title: "crash_title", Report: []byte("log")}
+
+ select {
+ case <-env.diffCtx.patchedOnly:
+ t.Fatal("unexpected patched only report")
+ case <-env.diffCtx.cfg.BaseCrashes: // Should report to BaseCrashes.
+ // Expected.
+ case <-time.After(testTimeout):
+ t.Fatal("expected base crash report")
+ }
+
+ env.waitForStatus("crash_title", manager.DiffBugStatusCompleted)
+}
+
+func TestDiffFailBaseCrashEarly(t *testing.T) {
+ // Base crashes first -> Patched crashes same title -> No reproduction attempt.
+ runReproCalled := false
+ mockRunRepro := mockReproCallback("crash_title", nil, func() {
+ runReproCalled = true
+ })
+
+ env := newTestEnv(t, &Config{
+ runRepro: mockRunRepro,
+ })
+ defer env.close()
+
+ env.new.FinishCorpusTriage()
+ env.start()
+
+ env.base.CrashesCh <- &report.Report{Title: "crash_title", Report: []byte("log")}
+ select {
+ case <-env.diffCtx.cfg.BaseCrashes:
+ case <-time.After(testTimeout):
+ t.Fatal("expected base crash")
+ }
+ env.new.CrashesCh <- &report.Report{Title: "crash_title", Report: []byte("log")}
+ env.waitForStatus("crash_title", manager.DiffBugStatusIgnored)
+ assert.False(t, runReproCalled, "WaitRepro should not be called")
+}
+
+func TestDiffRetryRepro(t *testing.T) {
+ // Patched kernel crashes -> Repro fails -> Retried until max attempts.
+ reproCalled := make(chan struct{}, 10)
+ mockRunRepro := mockReproCallback("", errors.New("repro failed"), func() {
+ reproCalled <- struct{}{}
+ })
+
+ env := newTestEnv(t, &Config{
+ runRepro: mockRunRepro,
+ })
+ defer env.close()
+
+ env.new.FinishCorpusTriage()
+ env.start()
+
+ for i := 0; i <= maxReproAttempts; i++ {
+ env.new.CrashesCh <- &report.Report{Title: "crash_title", Report: []byte("log")}
+ select {
+ case <-reproCalled:
+ case <-time.After(testTimeout):
+ t.Fatalf("iteration %d: expected repro", i)
+ }
+ env.waitForStatus("crash_title", manager.DiffBugStatusCompleted)
+ }
+ // Inject one more crash, which should be ignored.
+ env.new.CrashesCh <- &report.Report{Title: "crash_title", Report: []byte("log")}
+ env.waitForStatus("crash_title", manager.DiffBugStatusIgnored)
+ select {
+ case <-reproCalled:
+ t.Fatalf("unexpected repro")
+ default:
+ }
+}
diff --git a/pkg/manager/diff/repro.go b/pkg/manager/diff/repro.go
index 8dbf907da..7c41bc13c 100644
--- a/pkg/manager/diff/repro.go
+++ b/pkg/manager/diff/repro.go
@@ -22,7 +22,6 @@ import (
type reproRunner struct {
done chan reproRunnerResult
running atomic.Int64
- kernel *kernelContext
}
type reproRunnerResult struct {
@@ -46,7 +45,7 @@ const (
// To avoid reporting false positives, the function does not require the kernel to crash with exactly
// the same crash title as in the original crash report. Any single crash is accepted.
// The result is sent back over the rr.done channel.
-func (rr *reproRunner) Run(ctx context.Context, r *repro.Result, fullRepro bool) {
+func (rr *reproRunner) Run(ctx context.Context, k Kernel, r *repro.Result, fullRepro bool) {
if r.Reliability < reliabilityCutOff {
log.Logf(1, "%s: repro is too unreliable, skipping", r.Report.Title)
return
@@ -56,12 +55,12 @@ func (rr *reproRunner) Run(ctx context.Context, r *repro.Result, fullRepro bool)
needRuns = 6
}
- pool := rr.kernel.pool
+ pool := k.Pool()
cnt := int(rr.running.Add(1))
pool.ReserveForRun(min(cnt, pool.Total()))
defer func() {
cnt := int(rr.running.Add(-1))
- rr.kernel.pool.ReserveForRun(min(cnt, pool.Total()))
+ pool.ReserveForRun(min(cnt, pool.Total()))
}()
ret := reproRunnerResult{reproReport: r.Report, repro: r, fullRepro: fullRepro}
@@ -80,7 +79,7 @@ func (rr *reproRunner) Run(ctx context.Context, r *repro.Result, fullRepro bool)
var result *instance.RunResult
runErr := pool.Run(ctx, func(ctx context.Context, inst *vm.Instance, updInfo dispatcher.UpdateInfo) {
var ret *instance.ExecProgInstance
- ret, err = instance.SetupExecProg(inst, rr.kernel.cfg, rr.kernel.reporter, nil)
+ ret, err = instance.SetupExecProg(inst, k.Config(), k.Reporter(), nil)
if err != nil {
return
}
@@ -114,3 +113,12 @@ func (rr *reproRunner) Run(ctx context.Context, r *repro.Result, fullRepro bool)
case <-ctx.Done():
}
}
+
+type runner interface {
+ Run(ctx context.Context, k Kernel, r *repro.Result, fullRepro bool)
+ Results() <-chan reproRunnerResult
+}
+
+func (rr *reproRunner) Results() <-chan reproRunnerResult {
+ return rr.done
+}