From 30c4bb8427fe0d12bfff0fea81bf31d04910129e Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Sun, 25 Jan 2026 21:01:16 +0000 Subject: pkg/manager/diff: add tests Refactor the package to support testing and add a number of basic tests. --- pkg/manager/diff/diff_test.go | 223 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 pkg/manager/diff/diff_test.go (limited to 'pkg/manager/diff/diff_test.go') 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 + } +} -- cgit mrf-deployment