aboutsummaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/repro/repro.go74
-rw-r--r--pkg/repro/repro_test.go35
2 files changed, 94 insertions, 15 deletions
diff --git a/pkg/repro/repro.go b/pkg/repro/repro.go
index 67fac3811..08ed610ec 100644
--- a/pkg/repro/repro.go
+++ b/pkg/repro/repro.go
@@ -379,29 +379,70 @@ func (ctx *context) extractProgBisect(entries []*prog.LogEntry, baseDuration tim
ctx.reproLogf(3, "bisect: trying to concatenate")
// Concatenate all programs into one.
- prog := &prog.Prog{
+ dur := duration(len(entries)) * 3 / 2
+ return ctx.concatenateProgs(entries, dur)
+}
+
+// The bisected progs may exceed the prog.MaxCalls limit.
+// So let's first try to drop unneeded calls.
+func (ctx *context) concatenateProgs(entries []*prog.LogEntry, dur time.Duration) (*Result, error) {
+ ctx.reproLogf(3, "bisect: concatenate %d entries", len(entries))
+ if len(entries) > 1 {
+ // There's a risk of exceeding prog.MaxCalls, so let's first minimize
+ // all entries separately.
+ for i := 0; i < len(entries); i++ {
+ ctx.reproLogf(1, "minimizing program #%d before concatenation", i)
+ callsBefore := len(entries[i].P.Calls)
+ entries[i].P, _ = prog.Minimize(entries[i].P, -1, prog.MinimizeParams{
+ RemoveCallsOnly: true,
+ },
+ func(p1 *prog.Prog, _ int) bool {
+ var newEntries []*prog.LogEntry
+ if i > 0 {
+ newEntries = append(newEntries, entries[:i]...)
+ }
+ newEntries = append(newEntries, &prog.LogEntry{
+ P: p1,
+ })
+ if i+1 < len(entries) {
+ newEntries = append(newEntries, entries[i+1:]...)
+ }
+ crashed, err := ctx.testProgs(newEntries, dur, ctx.startOpts)
+ if err != nil {
+ ctx.reproLogf(0, "concatenation step failed with %v", err)
+ return false
+ }
+ return crashed
+ })
+ ctx.reproLogf(1, "minimized %d calls -> %d calls", callsBefore, len(entries[i].P.Calls))
+ }
+ }
+ p := &prog.Prog{
Target: entries[0].P.Target,
}
for _, entry := range entries {
- prog.Calls = append(prog.Calls, entry.P.Calls...)
+ p.Calls = append(p.Calls, entry.P.Calls...)
}
- dur := duration(len(entries)) * 3 / 2
- crashed, err := ctx.testProg(prog, dur, opts)
+ if len(p.Calls) > prog.MaxCalls {
+ ctx.reproLogf(2, "bisect: concatenated prog still exceeds %d calls", prog.MaxCalls)
+ return nil, nil
+ }
+ crashed, err := ctx.testProg(p, dur, ctx.startOpts)
if err != nil {
+ ctx.reproLogf(3, "bisect: error during concatenation testing: %v", err)
return nil, err
}
- if crashed {
- res := &Result{
- Prog: prog,
- Duration: dur,
- Opts: opts,
- }
- ctx.reproLogf(3, "bisect: concatenation succeeded")
- return res, nil
+ if !crashed {
+ ctx.reproLogf(3, "bisect: concatenated prog does not crash")
+ return nil, nil
}
-
- ctx.reproLogf(3, "bisect: concatenation failed")
- return nil, nil
+ res := &Result{
+ Prog: p,
+ Duration: dur,
+ Opts: ctx.startOpts,
+ }
+ ctx.reproLogf(3, "bisect: concatenation succeeded")
+ return res, nil
}
// Minimize calls and arguments.
@@ -580,6 +621,9 @@ func (ctx *context) runOnInstance(callback func(execInterface) (rep *instance.Ru
func encodeEntries(entries []*prog.LogEntry) []byte {
buf := new(bytes.Buffer)
for _, ent := range entries {
+ if len(ent.P.Calls) > prog.MaxCalls {
+ panic("prog.MaxCalls is exceeded")
+ }
fmt.Fprintf(buf, "executing program %v:\n%v", ent.Proc, string(ent.P.Serialize()))
}
return buf.Bytes()
diff --git a/pkg/repro/repro_test.go b/pkg/repro/repro_test.go
index 3ceacae5f..3945eae0f 100644
--- a/pkg/repro/repro_test.go
+++ b/pkg/repro/repro_test.go
@@ -259,3 +259,38 @@ func TestTooManyErrors(t *testing.T) {
t.Fatalf("expected an error")
}
}
+
+func TestProgConcatenation(t *testing.T) {
+ // Since the crash condition is alarm() after pause(), the code
+ // would have to work around the prog.MaxCall limitation.
+ execLog := "2015/12/21 12:18:05 executing program 1:\n"
+ for i := 0; i < prog.MaxCalls; i++ {
+ if i == 10 {
+ execLog += "pause()\n"
+ } else {
+ execLog += "getpid()\n"
+ }
+ }
+ execLog += "2015/12/21 12:18:10 executing program 2:\n"
+ for i := 0; i < prog.MaxCalls; i++ {
+ if i == 10 {
+ execLog += "alarm(0xa)\n"
+ } else {
+ execLog += "getpid()\n"
+ }
+ }
+ ctx := prepareTestCtx(t, execLog)
+ go generateTestInstances(ctx, 3, &testExecInterface{
+ t: t,
+ run: testExecRunner,
+ })
+ 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)
+ }
+}