diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2026-01-25 21:01:16 +0000 |
|---|---|---|
| committer | Aleksandr Nogikh <nogikh@google.com> | 2026-01-26 15:43:37 +0000 |
| commit | 30c4bb8427fe0d12bfff0fea81bf31d04910129e (patch) | |
| tree | 6ff01bffdf8f23a3e76a1218a4b4af235e137f29 /pkg/manager/diff | |
| parent | dd170fcec9a15df2760e4cd84d22aab51ef0d172 (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.go | 223 | ||||
| -rw-r--r-- | pkg/manager/diff/kernel.go | 28 | ||||
| -rw-r--r-- | pkg/manager/diff/manager.go | 71 | ||||
| -rw-r--r-- | pkg/manager/diff/manager_test.go | 188 | ||||
| -rw-r--r-- | pkg/manager/diff/repro.go | 18 |
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 +} |
