aboutsummaryrefslogtreecommitdiffstats
path: root/dashboard/app/coverage_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'dashboard/app/coverage_test.go')
-rw-r--r--dashboard/app/coverage_test.go171
1 files changed, 171 insertions, 0 deletions
diff --git a/dashboard/app/coverage_test.go b/dashboard/app/coverage_test.go
new file mode 100644
index 000000000..ef8321c9f
--- /dev/null
+++ b/dashboard/app/coverage_test.go
@@ -0,0 +1,171 @@
+// Copyright 2025 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 (
+ "strings"
+ "testing"
+
+ "github.com/google/syzkaller/pkg/coveragedb"
+ "github.com/google/syzkaller/pkg/coveragedb/mocks"
+ "github.com/google/syzkaller/pkg/coveragedb/spannerclient"
+ "github.com/google/syzkaller/pkg/covermerger"
+ mergermocks "github.com/google/syzkaller/pkg/covermerger/mocks"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+ "google.golang.org/api/iterator"
+)
+
+func TestFileCoverage(t *testing.T) {
+ tests := []struct {
+ name string
+ covDB func(t *testing.T) spannerclient.SpannerClient
+ fileProv func(t *testing.T) covermerger.FileVersProvider
+ url string
+ wantInRes []string
+ }{
+ {
+ name: "empty db",
+ covDB: func(t *testing.T) spannerclient.SpannerClient { return emptyCoverageDBFixture(t, 1) },
+ fileProv: func(t *testing.T) covermerger.FileVersProvider { return staticFileProvider(t) },
+ url: "/test2/graph/coverage/file?dateto=2025-01-31&period=month" +
+ "&commit=c0e75905caf368e19aab585d20151500e750de89&filepath=virt/kvm/kvm_main.c",
+ wantInRes: []string{"1 line1"},
+ },
+ {
+ name: "regular db",
+ covDB: func(t *testing.T) spannerclient.SpannerClient { return coverageDBFixture(t) },
+ fileProv: func(t *testing.T) covermerger.FileVersProvider { return staticFileProvider(t) },
+ url: "/test2/graph/coverage/file?dateto=2025-01-31&period=month" +
+ "&commit=c0e75905caf368e19aab585d20151500e750de89&filepath=virt/kvm/kvm_main.c",
+ wantInRes: []string{
+ "4 1 line1",
+ "5 2 line2",
+ "6 3 line3"},
+ },
+ {
+ name: "multimanager db",
+ covDB: func(t *testing.T) spannerclient.SpannerClient { return multiManagerCovDBFixture(t) },
+ fileProv: func(t *testing.T) covermerger.FileVersProvider { return staticFileProvider(t) },
+ url: "/test2/graph/coverage/file?dateto=2025-01-31&period=month" +
+ "&commit=c0e75905caf368e19aab585d20151500e750de89&filepath=virt/kvm/kvm_main.c" +
+ "&manager=special-cc-manager&unique-only=1",
+ wantInRes: []string{
+ " 0 1 line1", // Covered, is not unique.
+ " 5 2 line2", // Covered and is unique.
+ " 3 line3", // Covered only by "*" managers.
+ },
+ },
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ c := NewCtx(t)
+ defer c.Close()
+ c.setCoverageMocks("test2", test.covDB(t), test.fileProv(t))
+ fileCovPage, err := c.GET(test.url)
+ assert.NoError(t, err)
+ got := string(fileCovPage)
+ for _, want := range test.wantInRes {
+ if !strings.Contains(got, want) {
+ t.Errorf(`"%s" wasn't found in "%s"'`, want, got)
+ }
+ }
+ })
+ }
+}
+
+func staticFileProvider(t *testing.T) covermerger.FileVersProvider {
+ m := mergermocks.NewFileVersProvider(t)
+ m.On("GetFileVersions", mock.Anything, mock.Anything).
+ Return(func(targetFilePath string, repoCommits ...covermerger.RepoCommit,
+ ) covermerger.FileVersions {
+ res := covermerger.FileVersions{}
+ for _, rc := range repoCommits {
+ res[rc] = `line1
+line2
+line3`
+ }
+ return res
+ }, nil)
+ return m
+}
+
+func emptyCoverageDBFixture(t *testing.T, times int) spannerclient.SpannerClient {
+ mRowIterator := mocks.NewRowIterator(t)
+ mRowIterator.On("Stop").Return().Times(times)
+ mRowIterator.On("Next").
+ Return(nil, iterator.Done).Times(times)
+
+ mTran := mocks.NewReadOnlyTransaction(t)
+ mTran.On("Query", mock.Anything, mock.Anything).
+ Return(mRowIterator).Times(times)
+
+ m := mocks.NewSpannerClient(t)
+ m.On("Single").
+ Return(mTran).Times(times)
+ return m
+}
+
+func coverageDBFixture(t *testing.T) spannerclient.SpannerClient {
+ mRowIt := newRowIteratorMock(t, []*coveragedb.LinesCoverage{{
+ LinesInstrumented: []int64{1, 2, 3},
+ HitCounts: []int64{4, 5, 6},
+ }})
+
+ mTran := mocks.NewReadOnlyTransaction(t)
+ mTran.On("Query", mock.Anything, mock.Anything).
+ Return(mRowIt).Once()
+
+ m := mocks.NewSpannerClient(t)
+ m.On("Single").
+ Return(mTran).Once()
+ return m
+}
+
+func multiManagerCovDBFixture(t *testing.T) spannerclient.SpannerClient {
+ mReadFullCoverageTran := mocks.NewReadOnlyTransaction(t)
+ mReadFullCoverageTran.On("Query", mock.Anything, mock.Anything).
+ Return(newRowIteratorMock(t, []*coveragedb.LinesCoverage{{
+ LinesInstrumented: []int64{1, 2, 3},
+ HitCounts: []int64{4, 5, 6},
+ }})).Once()
+
+ mReadPartialCoverageTran := mocks.NewReadOnlyTransaction(t)
+ mReadPartialCoverageTran.On("Query", mock.Anything, mock.Anything).
+ Return(newRowIteratorMock(t, []*coveragedb.LinesCoverage{{
+ LinesInstrumented: []int64{1, 2},
+ HitCounts: []int64{3, 5},
+ }})).Once()
+
+ m := mocks.NewSpannerClient(t)
+ // The order matters. Full coverage is fetched second.
+ m.On("Single").
+ Return(mReadPartialCoverageTran).Once()
+ m.On("Single").
+ Return(mReadFullCoverageTran).Once()
+
+ return m
+}
+
+func newRowIteratorMock(t *testing.T, cov []*coveragedb.LinesCoverage,
+) *mocks.RowIterator {
+ m := mocks.NewRowIterator(t)
+ m.On("Stop").Once().Return()
+ for _, item := range cov {
+ mRow := mocks.NewRow(t)
+ mRow.On("ToStruct", mock.Anything).
+ Run(func(args mock.Arguments) {
+ arg := args.Get(0).(*coveragedb.LinesCoverage)
+ *arg = *item
+ }).
+ Return(nil).Once()
+
+ m.On("Next").
+ Return(mRow, nil).Once()
+ }
+
+ m.On("Next").
+ Return(nil, iterator.Done).Once()
+ return m
+}