blob: 5aa987166bdb84e425f95a3145faf1f96acbb102 [file] [log] [blame] [edit]
// Copyright 2018 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 main
import (
"fmt"
"net/http"
"testing"
"time"
"github.com/google/syzkaller/dashboard/dashapi"
)
// Normal workflow:
// - upload crash -> need repro
// - upload syz repro -> still need repro
// - upload C repro -> don't need repro
func testNeedRepro1(t *testing.T, crashCtor func(c *Ctx) *dashapi.Crash, newBug bool) {
c := NewCtx(t)
defer c.Close()
crash1 := crashCtor(c)
resp, _ := c.client.ReportCrash(crash1)
c.expectEQ(resp.NeedRepro, true)
cid := testCrashID(crash1)
needRepro, _ := c.client.NeedRepro(cid)
c.expectEQ(needRepro, true)
// Still need repro for this crash.
resp, _ = c.client.ReportCrash(crash1)
c.expectEQ(resp.NeedRepro, true)
needRepro, _ = c.client.NeedRepro(cid)
c.expectEQ(needRepro, true)
crash2 := new(dashapi.Crash)
*crash2 = *crash1
crash2.ReproOpts = []byte("opts")
crash2.ReproSyz = []byte("repro syz")
resp, _ = c.client.ReportCrash(crash2)
c.expectEQ(resp.NeedRepro, true)
needRepro, _ = c.client.NeedRepro(cid)
c.expectEQ(needRepro, true)
// MayBeMissing flag must not affect bugs that actually exist.
cidMissing := testCrashID(crash1)
cidMissing.MayBeMissing = true
needRepro, _ = c.client.NeedRepro(cidMissing)
c.expectEQ(needRepro, true)
crash2.ReproC = []byte("repro C")
resp, _ = c.client.ReportCrash(crash2)
c.expectEQ(resp.NeedRepro, false)
needRepro, _ = c.client.NeedRepro(cid)
c.expectEQ(needRepro, false)
needRepro, _ = c.client.NeedRepro(cidMissing)
c.expectEQ(needRepro, false)
resp, _ = c.client.ReportCrash(crash2)
c.expectEQ(resp.NeedRepro, false)
if newBug {
c.client.pollBug()
}
}
func TestNeedRepro1_normal(t *testing.T) { testNeedRepro1(t, normalCrash, true) }
func TestNeedRepro1_dup(t *testing.T) { testNeedRepro1(t, dupCrash, false) }
func TestNeedRepro1_closed(t *testing.T) { testNeedRepro1(t, closedCrash, true) }
func TestNeedRepro1_closedRepro(t *testing.T) { testNeedRepro1(t, closedWithReproCrash, true) }
// Upload C repro with first crash -> don't need repro.
func testNeedRepro2(t *testing.T, crashCtor func(c *Ctx) *dashapi.Crash, newBug bool) {
c := NewCtx(t)
defer c.Close()
crash1 := crashCtor(c)
crash1.ReproOpts = []byte("opts")
crash1.ReproSyz = []byte("repro syz")
crash1.ReproC = []byte("repro C")
resp, _ := c.client.ReportCrash(crash1)
c.expectEQ(resp.NeedRepro, false)
needRepro, _ := c.client.NeedRepro(testCrashID(crash1))
c.expectEQ(needRepro, false)
if newBug {
c.client.pollBug()
}
}
func TestNeedRepro2_normal(t *testing.T) { testNeedRepro2(t, normalCrash, true) }
func TestNeedRepro2_dup(t *testing.T) { testNeedRepro2(t, dupCrash, false) }
func TestNeedRepro2_closed(t *testing.T) { testNeedRepro2(t, closedCrash, true) }
func TestNeedRepro2_closedRepro(t *testing.T) { testNeedRepro2(t, closedWithReproCrash, true) }
// Test that after uploading 5 failed repros, app stops requesting repros.
func testNeedRepro3(t *testing.T, crashCtor func(c *Ctx) *dashapi.Crash) {
c := NewCtx(t)
defer c.Close()
crash1 := crashCtor(c)
for i := 0; i < maxReproPerBug; i++ {
resp, _ := c.client.ReportCrash(crash1)
c.expectEQ(resp.NeedRepro, true)
needRepro, _ := c.client.NeedRepro(testCrashID(crash1))
c.expectEQ(needRepro, true)
c.client.ReportFailedRepro(testCrashID(crash1))
}
for i := 0; i < 3; i++ {
// No more repros today.
c.advanceTime(time.Hour)
resp, _ := c.client.ReportCrash(crash1)
c.expectEQ(resp.NeedRepro, false)
needRepro, _ := c.client.NeedRepro(testCrashID(crash1))
c.expectEQ(needRepro, false)
// Then another repro after a day.
c.advanceTime(25 * time.Hour)
for j := 0; j < 2; j++ {
resp, _ := c.client.ReportCrash(crash1)
c.expectEQ(resp.NeedRepro, true)
needRepro, _ := c.client.NeedRepro(testCrashID(crash1))
c.expectEQ(needRepro, true)
}
c.client.ReportFailedRepro(testCrashID(crash1))
}
}
func TestNeedRepro3_normal(t *testing.T) { testNeedRepro3(t, normalCrash) }
func TestNeedRepro3_dup(t *testing.T) { testNeedRepro3(t, dupCrash) }
func TestNeedRepro3_closed(t *testing.T) { testNeedRepro3(t, closedCrash) }
func TestNeedRepro3_closedRepro(t *testing.T) { testNeedRepro3(t, closedWithReproCrash) }
func normalCrash(c *Ctx) *dashapi.Crash {
build := testBuild(1)
c.client.UploadBuild(build)
crash := testCrash(build, 1)
c.client.ReportCrash(crash)
c.client.pollBug()
return crash
}
func dupCrash(c *Ctx) *dashapi.Crash {
build := testBuild(1)
c.client.UploadBuild(build)
c.client.ReportCrash(testCrash(build, 1))
crash2 := testCrash(build, 2)
c.client.ReportCrash(crash2)
reports := c.client.pollBugs(2)
c.client.updateBug(reports[1].ID, dashapi.BugStatusDup, reports[0].ID)
return crash2
}
func closedCrash(c *Ctx) *dashapi.Crash {
return closedCrashImpl(c, false)
}
func closedWithReproCrash(c *Ctx) *dashapi.Crash {
return closedCrashImpl(c, true)
}
func closedCrashImpl(c *Ctx, withRepro bool) *dashapi.Crash {
build := testBuild(1)
c.client.UploadBuild(build)
crash := testCrash(build, 1)
if withRepro {
crash.ReproC = []byte("repro C")
}
resp, _ := c.client.ReportCrash(crash)
c.expectEQ(resp.NeedRepro, !withRepro)
rep := c.client.pollBug()
c.client.updateBug(rep.ID, dashapi.BugStatusInvalid, "")
crash.ReproC = nil
c.client.ReportCrash(crash)
c.client.pollBug()
return crash
}
func TestNeedReproMissing(t *testing.T) {
c := NewCtx(t)
defer c.Close()
client := c.makeClient(client1, password1, false)
cid := &dashapi.CrashID{
BuildID: "some missing build",
Title: "some missing title",
}
needRepro, err := client.NeedRepro(cid)
c.expectNE(err, nil)
c.expectEQ(needRepro, false)
cid.MayBeMissing = true
needRepro, err = client.NeedRepro(cid)
c.expectEQ(err, nil)
c.expectEQ(needRepro, true)
}
// In addition to the above, do a number of quick tests of the needReproForBug function.
func TestNeedReproIsolated(t *testing.T) {
c := NewCtx(t)
defer c.Close()
nowTime := c.mockedTime
tests := []struct {
bug *Bug
needRepro bool
}{
{
// A bug without a repro.
bug: &Bug{
Title: "normal bug without a repro",
},
needRepro: true,
},
{
// Corrupted bug.
bug: &Bug{
Title: corruptedReportTitle,
},
needRepro: false,
},
{
// Suppressed bug.
bug: &Bug{
Title: suppressedReportTitle,
},
needRepro: false,
},
{
// A bug without a C repro.
bug: &Bug{
Title: "some normal bug with a syz-repro",
ReproLevel: ReproLevelSyz,
HeadReproLevel: ReproLevelSyz,
},
needRepro: true,
},
{
// A bug for which we have recently found a repro.
bug: &Bug{
Title: "some normal recent bug",
ReproLevel: ReproLevelC,
HeadReproLevel: ReproLevelC,
LastReproTime: nowTime.Add(-time.Hour * 24),
},
needRepro: false,
},
{
// A bug which has an old C repro.
bug: &Bug{
Title: "some normal bug with old repro",
ReproLevel: ReproLevelC,
HeadReproLevel: ReproLevelC,
NumRepro: 2 * maxReproPerBug,
LastReproTime: nowTime.Add(-reproStalePeriod),
},
needRepro: true,
},
{
// Several failed repro attepts are OK.
bug: &Bug{
Title: "some normal bug with several fails",
NumRepro: maxReproPerBug - 1,
LastReproTime: nowTime,
},
needRepro: true,
},
{
// ... but there are limits.
bug: &Bug{
Title: "some normal bug with too much fails",
NumRepro: maxReproPerBug,
LastReproTime: nowTime,
},
needRepro: false,
},
{
// Make sure we try until we find a C repro, not just a syz repro.
bug: &Bug{
Title: "too many fails, but only a syz repro",
ReproLevel: ReproLevelSyz,
HeadReproLevel: ReproLevelSyz,
NumRepro: maxReproPerBug,
LastReproTime: nowTime.Add(-24 * time.Hour),
},
needRepro: true,
},
{
// We don't need a C repro for SYZFATAL: bugs.
bug: &Bug{
Title: "SYZFATAL: Manager.Check call failed",
ReproLevel: ReproLevelSyz,
HeadReproLevel: ReproLevelSyz,
LastReproTime: nowTime.Add(-24 * time.Hour),
},
needRepro: false,
},
{
// .. and for SYZFAIL: bugs.
bug: &Bug{
Title: "SYZFAIL: clock_gettime failed",
ReproLevel: ReproLevelSyz,
HeadReproLevel: ReproLevelSyz,
LastReproTime: nowTime.Add(-24 * time.Hour),
},
needRepro: false,
},
{
// Yet make sure that we request at least a syz repro.
bug: &Bug{
Title: "SYZFATAL: Manager.Check call failed",
},
needRepro: true,
},
{
// A bug with a revoked repro.
bug: &Bug{
Title: "some normal bug with a syz-repro",
ReproLevel: ReproLevelC,
HeadReproLevel: ReproLevelSyz,
LastReproTime: nowTime.Add(-24 * time.Hour),
},
needRepro: true,
},
}
for _, test := range tests {
bug := test.bug
if bug.Namespace == "" {
bug.Namespace = "test1"
}
funcResult := needReproForBug(c.ctx, bug)
if funcResult != test.needRepro {
t.Errorf("For %#v expected needRepro=%v, got needRepro=%v",
bug, test.needRepro, funcResult)
}
}
}
func TestFailedReproLogs(t *testing.T) {
c := NewCtx(t)
defer c.Close()
build := testBuild(1)
c.client.UploadBuild(build)
crash1 := &dashapi.Crash{
BuildID: "build1",
Title: "title1",
Log: []byte("log1"),
Report: []byte("report1"),
}
c.client.ReportCrash(crash1)
resp, _ := c.client.ReportingPollBugs("test")
c.expectEQ(len(resp.Reports), 1)
rep := resp.Reports[0]
c.client.ReportingUpdate(&dashapi.BugUpdate{
ID: rep.ID,
Status: dashapi.BugStatusOpen,
})
// Report max attempts.
cid := &dashapi.CrashID{
BuildID: crash1.BuildID,
Title: crash1.Title,
}
for i := 0; i < maxReproLogs; i++ {
c.advanceTime(time.Minute)
cid.ReproLog = []byte(fmt.Sprintf("report log %#v", i))
err := c.client.ReportFailedRepro(cid)
c.expectOK(err)
}
dbBug, _, _ := c.loadBug(rep.ID)
firstRecords := dbBug.ReproAttempts
c.expectEQ(len(firstRecords), maxReproLogs)
// Report one more.
cid.ReproLog = []byte(fmt.Sprintf("report log %#v", maxReproLogs))
err := c.client.ReportFailedRepro(cid)
c.expectOK(err)
dbBug, _, _ = c.loadBug(rep.ID)
lastRecords := dbBug.ReproAttempts
c.expectEQ(len(firstRecords), maxReproLogs)
// Ensure the first record was dropped.
checkResponseStatusCode(c, AccessAdmin,
textLink(textReproLog, firstRecords[0].Log), http.StatusNotFound)
// Ensure that the second record is readable.
reply, err := c.AuthGET(AccessAdmin, textLink(textReproLog, lastRecords[0].Log))
c.expectOK(err)
c.expectEQ(reply, []byte("report log 1"))
}