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/manager_test.go | 188 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) (limited to 'pkg/manager/diff/manager_test.go') 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: + } +} -- cgit mrf-deployment