Skip to content

Commit 30c4bb8

Browse files
committed
pkg/manager/diff: add tests
Refactor the package to support testing and add a number of basic tests.
1 parent dd170fc commit 30c4bb8

File tree

5 files changed

+497
-31
lines changed

5 files changed

+497
-31
lines changed

pkg/manager/diff/diff_test.go

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
// Copyright 2026 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 diff
5+
6+
import (
7+
"context"
8+
"testing"
9+
"time"
10+
11+
"github.com/google/syzkaller/pkg/flatrpc"
12+
"github.com/google/syzkaller/pkg/manager"
13+
"github.com/google/syzkaller/pkg/mgrconfig"
14+
"github.com/google/syzkaller/pkg/report"
15+
"github.com/google/syzkaller/pkg/repro"
16+
"github.com/google/syzkaller/prog"
17+
_ "github.com/google/syzkaller/prog/test"
18+
"github.com/google/syzkaller/vm"
19+
"github.com/google/syzkaller/vm/dispatcher"
20+
)
21+
22+
type testEnv struct {
23+
t *testing.T
24+
ctx context.Context
25+
cancel context.CancelFunc
26+
diffCtx *diffContext
27+
base *MockKernel
28+
new *MockKernel
29+
done chan error
30+
}
31+
32+
func newTestEnv(t *testing.T, cfg *Config) *testEnv {
33+
if cfg == nil {
34+
cfg = &Config{}
35+
}
36+
if cfg.Store == nil {
37+
cfg.Store = &manager.DiffFuzzerStore{BasePath: t.TempDir()}
38+
}
39+
if cfg.PatchedOnly == nil {
40+
cfg.PatchedOnly = make(chan *Bug, 1)
41+
}
42+
if cfg.BaseCrashes == nil {
43+
cfg.BaseCrashes = make(chan string, 1)
44+
}
45+
if cfg.runner == nil {
46+
// Default to a no-op runner if none provided.
47+
cfg.runner = newMockRunner(nil)
48+
}
49+
50+
diffCtx := &diffContext{
51+
cfg: *cfg,
52+
doneRepro: make(chan *manager.ReproResult, 1),
53+
store: cfg.Store,
54+
reproAttempts: map[string]int{},
55+
patchedOnly: cfg.PatchedOnly,
56+
}
57+
58+
base := &MockKernel{
59+
CrashesCh: make(chan *report.Report, 1),
60+
LoopFunc: func(ctx context.Context) error { return nil },
61+
}
62+
newKernel := &MockKernel{
63+
CrashesCh: make(chan *report.Report, 1),
64+
LoopFunc: func(ctx context.Context) error { return nil },
65+
}
66+
67+
newKernel.PoolVal = dispatcher.NewPool[*vm.Instance](1, func(ctx context.Context, index int) (*vm.Instance, error) {
68+
return &vm.Instance{}, nil
69+
}, func(ctx context.Context, inst *vm.Instance, upd dispatcher.UpdateInfo) {
70+
})
71+
newKernel.ConfigVal = &mgrconfig.Config{}
72+
73+
diffCtx.base = base
74+
diffCtx.new = newKernel
75+
76+
ctx, cancel := context.WithCancel(context.Background())
77+
78+
return &testEnv{
79+
t: t,
80+
ctx: ctx,
81+
cancel: cancel,
82+
diffCtx: diffCtx,
83+
base: base,
84+
new: newKernel,
85+
done: make(chan error, 1),
86+
}
87+
}
88+
89+
func (env *testEnv) start() {
90+
go func() {
91+
env.done <- env.diffCtx.Loop(env.ctx)
92+
}()
93+
}
94+
95+
func (env *testEnv) close() {
96+
env.cancel()
97+
select {
98+
case <-env.done:
99+
case <-time.After(5 * time.Second):
100+
env.t.Error("timeout waiting for diffCtx loop to exit")
101+
}
102+
}
103+
104+
func (env *testEnv) waitForStatus(title string, status manager.DiffBugStatus) {
105+
env.t.Helper()
106+
start := time.Now()
107+
for time.Since(start) < 15*time.Second {
108+
for _, bug := range env.diffCtx.store.List() {
109+
if bug.Title == title && bug.Status == status {
110+
return
111+
}
112+
}
113+
time.Sleep(10 * time.Millisecond)
114+
}
115+
env.t.Fatalf("timed out waiting for status: %s", status)
116+
}
117+
118+
type MockKernel struct {
119+
LoopFunc func(ctx context.Context) error
120+
CrashesCh chan *report.Report
121+
TriageProgressVal float64
122+
ProgsPerAreaVal map[string]int
123+
CoverFiltersVal manager.CoverageFilters
124+
ConfigVal *mgrconfig.Config
125+
PoolVal *vm.Dispatcher
126+
FeaturesVal flatrpc.Feature
127+
ReporterVal *report.Reporter
128+
}
129+
130+
func (mk *MockKernel) Loop(ctx context.Context) error {
131+
if mk.LoopFunc != nil {
132+
return mk.LoopFunc(ctx)
133+
}
134+
<-ctx.Done()
135+
return nil
136+
}
137+
138+
func (mk *MockKernel) Crashes() <-chan *report.Report {
139+
return mk.CrashesCh
140+
}
141+
142+
func (mk *MockKernel) TriageProgress() float64 {
143+
return mk.TriageProgressVal
144+
}
145+
146+
func (mk *MockKernel) ProgsPerArea() map[string]int {
147+
return mk.ProgsPerAreaVal
148+
}
149+
150+
func (mk *MockKernel) CoverFilters() manager.CoverageFilters {
151+
return mk.CoverFiltersVal
152+
}
153+
154+
func (mk *MockKernel) Config() *mgrconfig.Config {
155+
return mk.ConfigVal
156+
}
157+
158+
func (mk *MockKernel) Pool() *vm.Dispatcher {
159+
return mk.PoolVal
160+
}
161+
162+
func (mk *MockKernel) Features() flatrpc.Feature {
163+
return mk.FeaturesVal
164+
}
165+
166+
func (mk *MockKernel) Reporter() *report.Reporter {
167+
return mk.ReporterVal
168+
}
169+
170+
func (mk *MockKernel) FinishCorpusTriage() {
171+
mk.TriageProgressVal = 1.0
172+
}
173+
174+
type mockRunner struct {
175+
runFunc func(ctx context.Context, k Kernel, r *repro.Result, fullRepro bool) *reproRunnerResult
176+
doneCh chan reproRunnerResult
177+
}
178+
179+
func newMockRunner(cb func(context.Context, Kernel, *repro.Result, bool) *reproRunnerResult) *mockRunner {
180+
return &mockRunner{
181+
runFunc: cb,
182+
doneCh: make(chan reproRunnerResult, 1),
183+
}
184+
}
185+
186+
func (m *mockRunner) Run(ctx context.Context, k Kernel, r *repro.Result, fullRepro bool) {
187+
if m.runFunc != nil {
188+
res := m.runFunc(ctx, k, r, fullRepro)
189+
if res != nil {
190+
m.doneCh <- *res
191+
}
192+
}
193+
}
194+
195+
func (m *mockRunner) Results() <-chan reproRunnerResult {
196+
return m.doneCh
197+
}
198+
199+
func mockRepro(title string, err error) func(context.Context, []byte, repro.Environment) (
200+
*repro.Result, *repro.Stats, error) {
201+
return mockReproCallback(title, err, nil)
202+
}
203+
204+
func mockReproCallback(title string, returnErr error,
205+
callback func()) func(context.Context, []byte, repro.Environment) (
206+
*repro.Result, *repro.Stats, error) {
207+
return func(ctx context.Context, crashLog []byte, env repro.Environment) (*repro.Result, *repro.Stats, error) {
208+
if callback != nil {
209+
callback()
210+
}
211+
if returnErr != nil {
212+
return nil, nil, returnErr
213+
}
214+
target, err := prog.GetTarget("test", "64")
215+
if err != nil {
216+
return nil, nil, err
217+
}
218+
return &repro.Result{
219+
Report: &report.Report{Title: title},
220+
Prog: target.DataMmapProg(),
221+
}, &repro.Stats{}, nil
222+
}
223+
}

pkg/manager/diff/kernel.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ func (kc *kernelContext) runInstance(ctx context.Context, inst *vm.Instance,
297297
return nil, err
298298
}
299299

300-
func (kc *kernelContext) triageProgress() float64 {
300+
func (kc *kernelContext) TriageProgress() float64 {
301301
fuzzer := kc.fuzzer.Load()
302302
if fuzzer == nil {
303303
return 0
@@ -310,10 +310,34 @@ func (kc *kernelContext) triageProgress() float64 {
310310
return 1.0 - float64(fuzzer.CandidatesToTriage())/float64(total)
311311
}
312312

313-
func (kc *kernelContext) progsPerArea() map[string]int {
313+
func (kc *kernelContext) ProgsPerArea() map[string]int {
314314
fuzzer := kc.fuzzer.Load()
315315
if fuzzer == nil {
316316
return nil
317317
}
318318
return fuzzer.Config.Corpus.ProgsPerArea()
319319
}
320+
321+
func (kc *kernelContext) Crashes() <-chan *report.Report {
322+
return kc.crashes
323+
}
324+
325+
func (kc *kernelContext) CoverFilters() manager.CoverageFilters {
326+
return kc.coverFilters
327+
}
328+
329+
func (kc *kernelContext) Config() *mgrconfig.Config {
330+
return kc.cfg
331+
}
332+
333+
func (kc *kernelContext) Pool() *vm.Dispatcher {
334+
return kc.pool
335+
}
336+
337+
func (kc *kernelContext) Features() flatrpc.Feature {
338+
return kc.features
339+
}
340+
341+
func (kc *kernelContext) Reporter() *report.Reporter {
342+
return kc.reporter
343+
}

0 commit comments

Comments
 (0)