blob: 890416ecc01f7a7468c17aa0913b0b0b8ca76000 [file] [log] [blame]
package deduplicator
import (
"fmt"
"testing"
"github.com/stretchr/testify/mock"
"go.skia.org/infra/fuzzer/go/data"
"go.skia.org/infra/fuzzer/go/tests"
"go.skia.org/infra/go/testutils"
)
func TestLocalSimpleDeduplication(t *testing.T) {
testutils.SmallTest(t)
d := NewLocalDeduplicator()
r1 := data.MockReport("skpicture", "aaaa")
r2 := data.MockReport("skpicture", "bbbb")
// mock report ffff and aaaa are the same, except for the name.
r3 := data.MockReport("skpicture", "ffff")
// mock report jjjj and aaaa are the same, except for the name and architecture.
r4 := data.MockReport("skpicture", "jjjj")
if !d.IsUnique(r1) {
t.Errorf("The deduplicator has not seen %#v, but said it has", r1)
}
if !d.IsUnique(r2) {
t.Errorf("The deduplicator has not seen %#v, but said it has", r2)
}
if d.IsUnique(r1) {
t.Errorf("Should not have said %#v was unique, it just saw it.", r1)
}
if d.IsUnique(r3) {
t.Errorf("Should not have said %#v was unique, it just saw something like it.", r3)
}
if !d.IsUnique(r4) {
t.Errorf("The deduplicator has not seen %#v, but said it has", r4)
}
}
func TestLocalUnknownStacktraces(t *testing.T) {
testutils.SmallTest(t)
d := NewLocalDeduplicator()
// mock report ee has no stacktrace for either. It should not be considered a duplicate, ever.
r1 := data.MockReport("skpicture", "eeee")
if !d.IsUnique(r1) {
t.Errorf("The deduplicator has not seen %#v, but said it has", r1)
}
if !d.IsUnique(r1) {
t.Errorf("Should not have said %#v was not unique, unknown stacktraces don't count.", r1)
}
}
func TestKey(t *testing.T) {
testutils.SmallTest(t)
// r1 is a report with 6 and 7 stacktrace frames for Debug/Release
r1 := makeReport()
debugStacktrace := data.StackTrace{}
releaseStacktrace := data.StackTrace{}
debugStacktrace.Frames = append(r1.Stacktraces["CLANG_DEBUG"].Frames, data.StackTraceFrame{})
// Intentionally add one frame to the debugStacktrace and set it as release
releaseStacktrace.Frames = append(debugStacktrace.Frames, data.StackTraceFrame{})
r1.Stacktraces["CLANG_DEBUG"] = debugStacktrace
r1.Stacktraces["CLANG_RELEASE"] = releaseStacktrace
k1 := key(r1)
k2 := key(r1)
if k1 != k2 {
t.Errorf("Keys should be deterministic\n%s != %s", k1, k2)
}
if n := len(debugStacktrace.Frames); n != _MAX_STACKTRACE_LINES+1 {
t.Errorf("key() should not have changed the report - it now has %d frames instead of 6", n)
t.Errorf(debugStacktrace.String())
}
if n := len(releaseStacktrace.Frames); n != _MAX_STACKTRACE_LINES+2 {
t.Errorf("key() should not have changed the report - it now has %d frames instead of 7", n)
t.Errorf(debugStacktrace.String())
}
if frame := debugStacktrace.Frames[0]; frame.LineNumber == 0 {
t.Errorf("key() should not have changed the report - it now has the wrong line number at index 0: %s", debugStacktrace.String())
}
if frame := releaseStacktrace.Frames[0]; frame.LineNumber == 0 {
t.Errorf("key() should not have changed the report - it now has the wrong line number at index 0: %s", releaseStacktrace.String())
}
}
func TestLocalLinesOfStacktrace(t *testing.T) {
testutils.SmallTest(t)
d := NewLocalDeduplicator()
r1 := makeReport()
r2 := makeReport()
debugStacktrace := data.StackTrace{}
debugStacktrace.Frames = append(r2.Stacktraces["CLANG_DEBUG"].Frames, data.StackTraceFrame{})
r3 := makeReport()
releaseStacktrace := data.StackTrace{}
releaseStacktrace.Frames = append(r2.Stacktraces["CLANG_RELEASE"].Frames, data.StackTraceFrame{})
if !d.IsUnique(r1) {
t.Errorf("The deduplicator has not seen %#v, but said it has", r1)
}
if d.IsUnique(r2) {
t.Errorf("Should not have said %#v was unique, it just saw something with the same top %d stacktraces.", r2, _MAX_STACKTRACE_LINES)
t.Errorf("r1 stacktraces: \n%#v", r1.Stacktraces)
t.Errorf("r2 stacktraces: \n%#v", r2.Stacktraces)
}
if d.IsUnique(r3) {
t.Errorf("Should not have said %#v was unique, it just saw something with the same top %d stacktraces.", r3, _MAX_STACKTRACE_LINES)
}
}
func TestLocalLineNumbers(t *testing.T) {
testutils.SmallTest(t)
d := NewLocalDeduplicator()
r1 := makeReport()
r2 := makeReport()
r2.Stacktraces["CLANG_DEBUG"].Frames[0].LineNumber = 9999
r3 := makeReport()
r3.Stacktraces["CLANG_RELEASE"].Frames[0].LineNumber = 9999
if !d.IsUnique(r1) {
t.Errorf("The deduplicator has not seen %#v, but said it has", r1)
}
if d.IsUnique(r2) {
t.Errorf("Should not have said %#v was unique, it just saw something with the same top %d stacktraces, not counting line numbers.", r2, _MAX_STACKTRACE_LINES)
t.Errorf("r1 stacktraces: \n%#v", r1.Stacktraces)
t.Errorf("r2 stacktraces: \n%#v", r2.Stacktraces)
}
if d.IsUnique(r3) {
t.Errorf("Should not have said %#v was unique, it just saw something with the same top %d stacktraces, not counting line numbers.", r3, _MAX_STACKTRACE_LINES)
t.Errorf("r1 stacktraces: \n%#v", r1.Stacktraces)
t.Errorf("r3 stacktraces: \n%#v", r3.Stacktraces)
}
}
func TestLocalFlags(t *testing.T) {
testutils.SmallTest(t)
d := NewLocalDeduplicator()
r1 := makeReport()
r2 := makeReport()
r2.Flags["CLANG_RELEASE"] = makeFlags(4, 2)
r3 := makeReport()
r3.Flags["CLANG_DEBUG"] = makeFlags(4, 2)
if !d.IsUnique(r1) {
t.Errorf("The deduplicator has not seen %#v, but said it has", r1)
}
if !d.IsUnique(r2) {
t.Errorf("The deduplicator has not seen %#v, but said it has", r2)
t.Errorf("Release flags: \n%s\n\n%s", r1.Flags["CLANG_RELEASE"], r2.Flags["CLANG_RELEASE"])
}
if !d.IsUnique(r3) {
t.Errorf("The deduplicator has not seen %#v, but said it has", r3)
t.Errorf("Release flags: \n%s\n\n%s", r1.Flags["CLANG_RELEASE"], r3.Flags["CLANG_RELEASE"])
}
}
func TestLocalCategory(t *testing.T) {
testutils.SmallTest(t)
d := NewLocalDeduplicator()
r1 := makeReport()
r2 := makeReport()
r2.FuzzCategory = "something else"
if !d.IsUnique(r1) {
t.Errorf("The deduplicator has not seen %#v, but said it has", r1)
}
if !d.IsUnique(r2) {
t.Errorf("The deduplicator has not seen %#v, but said it has", r2)
}
}
func TestLocalArchitecture(t *testing.T) {
testutils.SmallTest(t)
d := NewLocalDeduplicator()
r1 := makeReport()
r2 := makeReport()
r2.FuzzArchitecture = "something else"
if !d.IsUnique(r1) {
t.Errorf("The deduplicator has not seen %#v, but said it has", r1)
}
if !d.IsUnique(r2) {
t.Errorf("The deduplicator has not seen %#v, but said it has", r2)
}
}
func TestLocalOther(t *testing.T) {
testutils.SmallTest(t)
d := NewLocalDeduplicator()
r1 := makeReport()
r1.Flags["CLANG_DEBUG"] = append(r1.Flags["CLANG_DEBUG"], "Other")
r2 := makeReport()
r2.Flags["CLANG_RELEASE"] = append(r2.Flags["CLANG_RELEASE"], "Other")
if !d.IsUnique(r1) {
t.Errorf("The deduplicator has not seen %#v, but said it has", r1)
}
if !d.IsUnique(r1) {
t.Errorf("The deduplicator should have said %#v was unique. The flag 'Other' should not be filtered.", r1)
}
if !d.IsUnique(r2) {
t.Errorf("The deduplicator has not seen %#v, but said it has", r2)
}
if !d.IsUnique(r2) {
t.Errorf("The deduplicator should have said %#v was unique. The flag 'Other' should not be filtered.", r2)
}
}
var ctx = mock.AnythingOfType("*context.emptyCtx")
func TestRemoteLookup(t *testing.T) {
testutils.SmallTest(t)
m := tests.NewMockGCSClient()
defer m.AssertExpectations(t)
d := NewRemoteDeduplicator(m)
d.SetRevision("COMMIT_HASH")
r1 := data.MockReport("skpicture", "aaaa")
r2 := data.MockReport("skpicture", "bbbb")
// hash for r1
// If the key algorithm ever changes, the hash here will need to change as well.
m.On("GetFileContents", ctx, "skpicture/COMMIT_HASH/mock_arm8/traces/20ed871935578a9deafbc755159e4ac389745a12").Return([]byte("20ed871935578a9deafbc755159e4ac389745a12"), nil)
if d.IsUnique(r1) {
t.Errorf("The deduplicator should have found %#v remotely, but said it didn't", r1)
}
m.On("GetFileContents", ctx, "skpicture/COMMIT_HASH/mock_arm8/traces/8f5de8e5d95579a935854da4a24b8a42e895e799").Return([]byte(nil), fmt.Errorf("Not found"))
m.On("SetFileContents", ctx, "skpicture/COMMIT_HASH/mock_arm8/traces/8f5de8e5d95579a935854da4a24b8a42e895e799", FILE_WRITE_OPTS, []byte("8f5de8e5d95579a935854da4a24b8a42e895e799")).Return(nil)
if !d.IsUnique(r2) {
t.Errorf("The deduplicator has not seen %#v, but said it has", r2)
}
m.AssertNumberOfCalls(t, "GetFileContents", 2)
m.AssertNumberOfCalls(t, "SetFileContents", 1)
}
func TestRemoteLookupWithLocalCache(t *testing.T) {
testutils.SmallTest(t)
m := tests.NewMockGCSClient()
defer m.AssertExpectations(t)
d := NewRemoteDeduplicator(m)
d.SetRevision("COMMIT_HASH")
r1 := data.MockReport("skpicture", "aaaa")
// If the key algorithm ever changes, the hash will need to change as well.
m.On("GetFileContents", ctx, "skpicture/COMMIT_HASH/mock_arm8/traces/20ed871935578a9deafbc755159e4ac389745a12").Return([]byte("20ed871935578a9deafbc755159e4ac389745a12"), nil)
if d.IsUnique(r1) {
t.Errorf("The deduplicator has seen %#v, but said it has not", r1)
}
if d.IsUnique(r1) {
t.Errorf("The deduplicator has seen %#v, but said it has not", r1)
}
if d.IsUnique(r1) {
t.Errorf("The deduplicator has seen %#v, but said it has not", r1)
}
// The Remote lookup should keep a local copy too.
m.AssertNumberOfCalls(t, "GetFileContents", 1)
}
func TestRemoteLookupReset(t *testing.T) {
testutils.SmallTest(t)
m := tests.NewMockGCSClient()
defer m.AssertExpectations(t)
d := NewRemoteDeduplicator(m)
d.SetRevision("COMMIT_HASH")
r1 := data.MockReport("skpicture", "aaaa")
// AnythingofType("[]byte") doesn't work because https://github.com/stretchr/testify/issues/387
m.On("SetFileContents", ctx, mock.AnythingOfType("string"), FILE_WRITE_OPTS, mock.AnythingOfType("[]uint8")).Return(nil)
// If the key algorithm ever changes, the hash will need to change as well.
m.On("GetFileContents", ctx, "skpicture/COMMIT_HASH/mock_arm8/traces/20ed871935578a9deafbc755159e4ac389745a12").Return([]byte(nil), fmt.Errorf("Not found"))
if !d.IsUnique(r1) {
t.Errorf("The deduplicator has not seen %#v, but said it has", r1)
}
m.On("GetFileContents", ctx, "skpicture/THE_SECOND_COMMIT_HASH/mock_arm8/traces/20ed871935578a9deafbc755159e4ac389745a12").Return([]byte(nil), fmt.Errorf("Not found")).Once()
d.SetRevision("THE_SECOND_COMMIT_HASH")
if !d.IsUnique(r1) {
t.Errorf("The deduplicator has not seen %#v, but said it has", r1)
}
// The Remote lookup should have to relookup the file after commit changed.
m.AssertNumberOfCalls(t, "GetFileContents", 2)
m.AssertNumberOfCalls(t, "SetFileContents", 2)
}
// Makes a report with the smallest stacktraces distinguishable by the deduplicator, 3 debug
// flags, 3 release flags and a standard name and category
func makeReport() data.FuzzReport {
ds := makeStacktrace(0)
rs := makeStacktrace(3)
df := makeFlags(0, 3)
rf := makeFlags(1, 2)
return data.FuzzReport{
Stacktraces: map[string]data.StackTrace{
"ASAN_DEBUG": ds,
"CLANG_DEBUG": ds,
"ASAN_RELEASE": rs,
"CLANG_RELEASE": rs,
},
Flags: map[string][]string{
"ASAN_DEBUG": df,
"CLANG_DEBUG": df,
"ASAN_RELEASE": rf,
"CLANG_RELEASE": rf,
},
FuzzName: "doesn't matter",
FuzzCategory: "api",
FuzzArchitecture: "mock_x64",
}
}
var names = []string{"alpha", "beta", "gamma", "delta", "epsilon", "zeta", "theta", "iota", "kappa", "lambda", "mu"}
func makeStacktrace(start int) data.StackTrace {
st := data.StackTrace{}
r := start
n := len(names)
for i := 0; i < _MAX_STACKTRACE_LINES; i++ {
a, b, c, d := r%n, (r+1)%n, (r+2)%n, (r+3)%n
st.Frames = append(st.Frames, data.FullStackFrame(names[a], names[b], names[c], d))
r = (r + 4) % n
}
return st
}
func makeFlags(start, count int) []string {
return names[start:(start + count)]
}