diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2023-05-24 17:40:41 +0200 |
|---|---|---|
| committer | Aleksandr Nogikh <wp32pw@gmail.com> | 2023-05-25 11:45:51 +0200 |
| commit | 453f56a20332a18969781dc145c8ae8da75468ea (patch) | |
| tree | 200eabe3b9e37f0e46561b5a22f9a68454cca1a1 | |
| parent | b36c307e4d7a49e7b738ab63576854eb43855e12 (diff) | |
pkg/repro: add a smoke test
This is a sanity test for the overall pkg/repro machinery. It does not
focus on minor corner cases.
| -rw-r--r-- | pkg/repro/repro.go | 12 | ||||
| -rw-r--r-- | pkg/repro/repro_test.go | 111 |
2 files changed, 117 insertions, 6 deletions
diff --git a/pkg/repro/repro.go b/pkg/repro/repro.go index 6df634960..b030d38bd 100644 --- a/pkg/repro/repro.go +++ b/pkg/repro/repro.go @@ -73,7 +73,7 @@ var ErrNoPrograms = errors.New("crash log does not contain any programs") func Run(crashLog []byte, cfg *mgrconfig.Config, features *host.Features, reporter *report.Reporter, vmPool *vm.Pool, vmIndexes []int) (*Result, *Stats, error) { - ctx, err := prepareCtx(crashLog, cfg, features, reporter, vmIndexes) + ctx, err := prepareCtx(crashLog, cfg, features, reporter, len(vmIndexes)) if err != nil { return nil, nil, err } @@ -82,8 +82,8 @@ func Run(crashLog []byte, cfg *mgrconfig.Config, features *host.Features, report } func prepareCtx(crashLog []byte, cfg *mgrconfig.Config, features *host.Features, reporter *report.Reporter, - vmIndexes []int) (*context, error) { - if len(vmIndexes) == 0 { + VMs int) (*context, error) { + if VMs == 0 { return nil, fmt.Errorf("no VMs provided") } entries := cfg.Target.ParseLog(crashLog) @@ -123,14 +123,14 @@ func prepareCtx(crashLog []byte, cfg *mgrconfig.Config, features *host.Features, crashType: crashType, crashStart: crashStart, entries: entries, - instances: make(chan *reproInstance, len(vmIndexes)), - bootRequests: make(chan int, len(vmIndexes)), + instances: make(chan *reproInstance, VMs), + bootRequests: make(chan int, VMs), testTimeouts: testTimeouts, startOpts: createStartOptions(cfg, features, crashType), stats: new(Stats), timeouts: cfg.Timeouts, } - ctx.reproLogf(0, "%v programs, %v VMs, timeouts %v", len(entries), len(vmIndexes), testTimeouts) + ctx.reproLogf(0, "%v programs, %v VMs, timeouts %v", len(entries), VMs, testTimeouts) return ctx, nil } diff --git a/pkg/repro/repro_test.go b/pkg/repro/repro_test.go index 486dc963b..51c99eb75 100644 --- a/pkg/repro/repro_test.go +++ b/pkg/repro/repro_test.go @@ -5,9 +5,16 @@ package repro import ( "math/rand" + "regexp" + "sync" "testing" + "time" + "github.com/google/go-cmp/cmp" "github.com/google/syzkaller/pkg/csource" + "github.com/google/syzkaller/pkg/instance" + "github.com/google/syzkaller/pkg/mgrconfig" + "github.com/google/syzkaller/pkg/report" "github.com/google/syzkaller/pkg/testutil" "github.com/google/syzkaller/prog" "github.com/google/syzkaller/sys/targets" @@ -98,3 +105,107 @@ func TestSimplifies(t *testing.T) { } check(opts, 0) } + +func generateTestInstances(ctx *context, count int, execInterface execInterface) { + for i := 0; i < count; i++ { + ctx.bootRequests <- i + } + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + for vmIndex := range ctx.bootRequests { + ctx.instances <- &reproInstance{execProg: execInterface, index: vmIndex} + } + }() + wg.Wait() + close(ctx.instances) +} + +type testExecInterface struct { + t *testing.T + // For now only do the simplest imitation. + run func([]byte) (*instance.RunResult, error) +} + +func (tei *testExecInterface) Close() {} + +func (tei *testExecInterface) RunCProg(p *prog.Prog, duration time.Duration, + opts csource.Options) (*instance.RunResult, error) { + return tei.RunSyzProg(p.Serialize(), duration, opts) +} + +func (tei *testExecInterface) RunSyzProg(syzProg []byte, duration time.Duration, + opts csource.Options) (*instance.RunResult, error) { + return tei.run(syzProg) +} + +func prepareTestCtx(t *testing.T, log string) *context { + mgrConfig := &mgrconfig.Config{ + Derived: mgrconfig.Derived{ + TargetOS: targets.Linux, + TargetVMArch: targets.AMD64, + }, + Sandbox: "namespace", + } + var err error + mgrConfig.Target, err = prog.GetTarget(targets.Linux, targets.AMD64) + if err != nil { + t.Fatal(err) + } + reporter, err := report.NewReporter(mgrConfig) + if err != nil { + t.Fatal(err) + } + ctx, err := prepareCtx([]byte(log), mgrConfig, nil, reporter, 3) + if err != nil { + t.Fatal(err) + } + return ctx +} + +const testReproLog = ` +2015/12/21 12:18:05 executing program 1: +getpid() +pause() +2015/12/21 12:18:10 executing program 2: +getpid() +getuid() +2015/12/21 12:18:15 executing program 1: +alarm(0x5) +pause() +2015/12/21 12:18:20 executing program 3: +alarm(0xa) +getpid() +` + +// Just a pkg/repro smoke test: check that we can extract a two-call reproducer. +// No focus on error handling and minor corner cases. +func TestPlainRepro(t *testing.T) { + ctx := prepareTestCtx(t, testReproLog) + // Only crash if `pause()` is followed by `alarm(0xa)`. + var match = regexp.MustCompile(`(?s)pause\(\).*alarm\(0xa\)`) + go generateTestInstances(ctx, 3, &testExecInterface{ + t: t, + run: func(log []byte) (*instance.RunResult, error) { + crash := match.Match(log) + if crash { + ret := &instance.RunResult{} + ret.Report = &report.Report{ + Title: `some crash`, + } + return ret, nil + } + return &instance.RunResult{}, nil + }, + }) + result, _, err := ctx.run() + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(`pause() +alarm(0xa) +`, string(result.Prog.Serialize())); diff != "" { + t.Fatal(diff) + } +} |
