aboutsummaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2021-01-17 12:25:56 +0100
committerDmitry Vyukov <dvyukov@google.com>2021-01-17 15:44:44 +0100
commitfbc119e631b5fe4eae43b1a37c660038ac3788cd (patch)
tree219f7c279261e624385ab3e2946ff76675553833 /pkg
parent813be5426a31b5b3ead90cf5729c8b7a7a17d7c1 (diff)
pkg/report: support alternative bug titles
Update #1575
Diffstat (limited to 'pkg')
-rw-r--r--pkg/report/freebsd.go3
-rw-r--r--pkg/report/linux.go5
-rw-r--r--pkg/report/report.go18
-rw-r--r--pkg/report/report_test.go36
4 files changed, 49 insertions, 13 deletions
diff --git a/pkg/report/freebsd.go b/pkg/report/freebsd.go
index e5a1fc92e..3bf0b8eb4 100644
--- a/pkg/report/freebsd.go
+++ b/pkg/report/freebsd.go
@@ -56,8 +56,9 @@ func (ctx *freebsd) Parse(output []byte) *Report {
if oops == nil {
return nil
}
- title, corrupted, _ := extractDescription(output[rep.StartPos:], oops, freebsdStackParams)
+ title, corrupted, altTitles, _ := extractDescription(output[rep.StartPos:], oops, freebsdStackParams)
rep.Title = title
+ rep.AltTitles = altTitles
rep.Corrupted = corrupted != ""
rep.CorruptedReason = corrupted
return rep
diff --git a/pkg/report/linux.go b/pkg/report/linux.go
index 8aed7d4f4..dd781ae89 100644
--- a/pkg/report/linux.go
+++ b/pkg/report/linux.go
@@ -145,17 +145,18 @@ func (ctx *linux) Parse(output []byte) *Report {
}
endPos, reportEnd, report, prefix := ctx.findReport(output, oops, startPos, context, questionable)
rep.EndPos = endPos
- title, corrupted, format := extractDescription(report[:reportEnd], oops, linuxStackParams)
+ title, corrupted, altTitles, format := extractDescription(report[:reportEnd], oops, linuxStackParams)
if title == "" {
prefix = nil
report = output[rep.StartPos:rep.EndPos]
- title, corrupted, format = extractDescription(report, oops, linuxStackParams)
+ title, corrupted, altTitles, format = extractDescription(report, oops, linuxStackParams)
if title == "" {
panic(fmt.Sprintf("non matching oops for %q context=%q in:\n%s\n",
oops.header, context, report))
}
}
rep.Title = title
+ rep.AltTitles = altTitles
rep.Corrupted = corrupted != ""
rep.CorruptedReason = corrupted
for _, line := range prefix {
diff --git a/pkg/report/report.go b/pkg/report/report.go
index 397684569..72d52bbf0 100644
--- a/pkg/report/report.go
+++ b/pkg/report/report.go
@@ -32,6 +32,9 @@ type Reporter interface {
type Report struct {
// Title contains a representative description of the first oops.
Title string
+ // Alternative titles, used for better deduplication.
+ // If two crashes have a non-empty intersection of Title/AltTitles, they are considered the same bug.
+ AltTitles []string
// Bug type (e.g. hang, memory leak, etc).
Type Type
// The indicative function name.
@@ -170,6 +173,9 @@ func (wrap *reporterWrapper) Parse(output []byte) *Report {
return nil
}
rep.Title = sanitizeTitle(replaceTable(dynamicTitleReplacement, rep.Title))
+ for i, title := range rep.AltTitles {
+ rep.AltTitles[i] = sanitizeTitle(replaceTable(dynamicTitleReplacement, title))
+ }
rep.Suppressed = matchesAny(rep.Output, wrap.suppressions)
if bytes.Contains(rep.Output, gceConsoleHangup) {
rep.Corrupted = true
@@ -329,6 +335,9 @@ type oopsFormat struct {
// Strings captured by title (or by report if present) are passed as input.
// If stack is not nil, extracted function name is passed as an additional last argument.
fmt string
+ // Alternative titles used for better crash deduplication.
+ // Format is the same as for fmt.
+ alt []string
// If not nil, a function name is extracted from the report and passed to fmt.
// If not nil but frame extraction fails, the report is considered corrupted.
stack *stackFmt
@@ -398,7 +407,8 @@ func matchOops(line []byte, oops *oops, ignores []*regexp.Regexp) bool {
return true
}
-func extractDescription(output []byte, oops *oops, params *stackParams) (desc, corrupted string, format oopsFormat) {
+func extractDescription(output []byte, oops *oops, params *stackParams) (
+ desc, corrupted string, altTitles []string, format oopsFormat) {
startPos := len(output)
matchedTitle := false
for _, f := range oops.formats {
@@ -438,6 +448,9 @@ func extractDescription(output []byte, oops *oops, params *stackParams) (desc, c
args = append(args, frame)
}
desc = fmt.Sprintf(f.fmt, args...)
+ for _, alt := range f.alt {
+ altTitles = append(altTitles, fmt.Sprintf(alt, args...))
+ }
format = f
}
if desc == "" {
@@ -591,8 +604,9 @@ func simpleLineParser(output []byte, oopses []*oops, params *stackParams, ignore
if oops == nil {
return nil
}
- title, corrupted, _ := extractDescription(output[rep.StartPos:], oops, params)
+ title, corrupted, altTitles, _ := extractDescription(output[rep.StartPos:], oops, params)
rep.Title = title
+ rep.AltTitles = altTitles
rep.Report = output[rep.StartPos:]
rep.Corrupted = corrupted != ""
rep.CorruptedReason = corrupted
diff --git a/pkg/report/report_test.go b/pkg/report/report_test.go
index 05883a21f..355edc764 100644
--- a/pkg/report/report_test.go
+++ b/pkg/report/report_test.go
@@ -10,7 +10,9 @@ import (
"fmt"
"io/ioutil"
"path/filepath"
+ "reflect"
"regexp"
+ "sort"
"strings"
"testing"
@@ -29,6 +31,7 @@ type ParseTest struct {
FileName string
Log []byte
Title string
+ AltTitles []string
Type Type
Frame string
StartLine string
@@ -92,6 +95,7 @@ func testParseFile(t *testing.T, reporter Reporter, fn string) {
func parseHeaderLine(t *testing.T, test *ParseTest, ln string) {
const (
titlePrefix = "TITLE: "
+ altTitlePrefix = "ALT: "
typePrefix = "TYPE: "
framePrefix = "FRAME: "
startPrefix = "START: "
@@ -103,6 +107,8 @@ func parseHeaderLine(t *testing.T, test *ParseTest, ln string) {
case strings.HasPrefix(ln, "#"):
case strings.HasPrefix(ln, titlePrefix):
test.Title = ln[len(titlePrefix):]
+ case strings.HasPrefix(ln, altTitlePrefix):
+ test.AltTitles = append(test.AltTitles, ln[len(altTitlePrefix):])
case strings.HasPrefix(ln, typePrefix):
switch v := ln[len(typePrefix):]; v {
case Hang.String():
@@ -159,23 +165,34 @@ func testParseImpl(t *testing.T, reporter Reporter, test *ParseTest) {
t.Fatalf("found crash, but title is empty")
}
title, corrupted, corruptedReason, suppressed, typ, frame := "", false, "", false, Unknown, ""
+ var altTitles []string
if rep != nil {
title = rep.Title
+ altTitles = rep.AltTitles
corrupted = rep.Corrupted
corruptedReason = rep.CorruptedReason
suppressed = rep.Suppressed
typ = rep.Type
frame = rep.Frame
}
- if title != test.Title || corrupted != test.Corrupted || suppressed != test.Suppressed ||
- typ != test.Type || test.Frame != "" && frame != test.Frame {
+ sort.Strings(altTitles)
+ sort.Strings(test.AltTitles)
+ if title != test.Title || !reflect.DeepEqual(altTitles, test.AltTitles) || corrupted != test.Corrupted ||
+ suppressed != test.Suppressed || typ != test.Type || test.Frame != "" && frame != test.Frame {
if *flagUpdate && test.StartLine+test.EndLine == "" {
- updateReportTest(t, test, title, corrupted, suppressed, typ, frame)
+ updateReportTest(t, test, title, altTitles, corrupted, suppressed, typ, frame)
}
- t.Fatalf("want:\nTITLE: %s\nTYPE: %v\nFRAME: %v\nCORRUPTED: %v\nSUPPRESSED: %v\n"+
- "got:\nTITLE: %s\nTYPE: %v\nFRAME: %v\nCORRUPTED: %v (%v)\nSUPPRESSED: %v\n",
- test.Title, test.Type, test.Frame, test.Corrupted, test.Suppressed,
- title, typ, frame, corrupted, corruptedReason, suppressed)
+ gotAltTitles, wantAltTitles := "", ""
+ for _, t := range altTitles {
+ gotAltTitles += "ALT: " + t + "\n"
+ }
+ for _, t := range test.AltTitles {
+ wantAltTitles += "ALT: " + t + "\n"
+ }
+ t.Fatalf("want:\nTITLE: %s\n%sTYPE: %v\nFRAME: %v\nCORRUPTED: %v\nSUPPRESSED: %v\n"+
+ "got:\nTITLE: %s\n%sTYPE: %v\nFRAME: %v\nCORRUPTED: %v (%v)\nSUPPRESSED: %v\n",
+ test.Title, wantAltTitles, test.Type, test.Frame, test.Corrupted, test.Suppressed,
+ title, gotAltTitles, typ, frame, corrupted, corruptedReason, suppressed)
}
if title != "" && len(rep.Report) == 0 {
t.Fatalf("found crash message but report is empty")
@@ -228,10 +245,13 @@ func checkReport(t *testing.T, reporter Reporter, rep *Report, test *ParseTest)
}
}
-func updateReportTest(t *testing.T, test *ParseTest, title string, corrupted, suppressed bool,
+func updateReportTest(t *testing.T, test *ParseTest, title string, altTitles []string, corrupted, suppressed bool,
typ Type, frame string) {
buf := new(bytes.Buffer)
fmt.Fprintf(buf, "TITLE: %v\n", title)
+ for _, t := range altTitles {
+ fmt.Fprintf(buf, "ALT: %v\n", t)
+ }
if typ != Unknown {
fmt.Fprintf(buf, "TYPE: %v\n", typ)
}