github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/dashboard/app/coverage_test.go (about)

     1  // Copyright 2025 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package main
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"net/http"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/google/syzkaller/pkg/coveragedb"
    14  	"github.com/google/syzkaller/pkg/coveragedb/mocks"
    15  	"github.com/google/syzkaller/pkg/coveragedb/spannerclient"
    16  	"github.com/google/syzkaller/pkg/covermerger"
    17  	mergermocks "github.com/google/syzkaller/pkg/covermerger/mocks"
    18  	"github.com/stretchr/testify/assert"
    19  	"github.com/stretchr/testify/mock"
    20  	"google.golang.org/api/iterator"
    21  )
    22  
    23  func setCoverageDBClient(ctx context.Context, client spannerclient.SpannerClient) context.Context {
    24  	return context.WithValue(ctx, &keyCoverageDBClient, client)
    25  }
    26  
    27  func TestFileCoverage_BadRequest(t *testing.T) {
    28  	badURL := "/test2/coverage/file?dateto=2025-01-31'&period=month" +
    29  		"&commit=c0e75905caf368e19aab585d20151500e750de89&filepath=virt/kvm/kvm_main.c"
    30  	c := NewCtx(t)
    31  	defer c.Close()
    32  	c.setCoverageMocks("test2", nil, nil)
    33  	_, err := c.GET(badURL)
    34  	var httpErr *HTTPError
    35  	assert.True(t, errors.As(err, &httpErr))
    36  	assert.Equal(t, http.StatusBadRequest, httpErr.Code)
    37  }
    38  
    39  func TestFileCoverage(t *testing.T) {
    40  	tests := []struct {
    41  		name      string
    42  		covDB     func(t *testing.T) spannerclient.SpannerClient
    43  		fileProv  func(t *testing.T) covermerger.FileVersProvider
    44  		url       string
    45  		wantInRes []string
    46  	}{
    47  		{
    48  			name:     "empty db",
    49  			covDB:    func(t *testing.T) spannerclient.SpannerClient { return emptyCoverageDBFixture(t, 1) },
    50  			fileProv: func(t *testing.T) covermerger.FileVersProvider { return staticFileProvider(t) },
    51  			url: "/test2/coverage/file?dateto=2025-01-31&period=month" +
    52  				"&commit=c0e75905caf368e19aab585d20151500e750de89&filepath=virt/kvm/kvm_main.c",
    53  			wantInRes: []string{"1 line1"},
    54  		},
    55  		{
    56  			name:     "regular db",
    57  			covDB:    func(t *testing.T) spannerclient.SpannerClient { return coverageDBFixture(t) },
    58  			fileProv: func(t *testing.T) covermerger.FileVersProvider { return staticFileProvider(t) },
    59  			url: "/test2/coverage/file?dateto=2025-01-31&period=month" +
    60  				"&commit=c0e75905caf368e19aab585d20151500e750de89&filepath=virt/kvm/kvm_main.c",
    61  			wantInRes: []string{
    62  				"4      1 line1",
    63  				"5      2 line2",
    64  				"6      3 line3"},
    65  		},
    66  		{
    67  			name:     "multimanager db",
    68  			covDB:    func(t *testing.T) spannerclient.SpannerClient { return multiManagerCovDBFixture(t) },
    69  			fileProv: func(t *testing.T) covermerger.FileVersProvider { return staticFileProvider(t) },
    70  			url: "/test2/coverage/file?dateto=2025-01-31&period=month" +
    71  				"&commit=c0e75905caf368e19aab585d20151500e750de89&filepath=virt/kvm/kvm_main.c" +
    72  				"&manager=special-cc-manager&unique-only=1",
    73  			wantInRes: []string{
    74  				" 0      1 line1", // Covered, is not unique.
    75  				" 5      2 line2", // Covered and is unique.
    76  				"        3 line3", // Covered only by "*" managers.
    77  			},
    78  		},
    79  	}
    80  	for _, test := range tests {
    81  		t.Run(test.name, func(t *testing.T) {
    82  			c := NewCtx(t)
    83  			defer c.Close()
    84  			c.setCoverageMocks("test2", test.covDB(t), test.fileProv(t))
    85  			fileCovPage, err := c.GET(test.url)
    86  			assert.NoError(t, err)
    87  			got := string(fileCovPage)
    88  			for _, want := range test.wantInRes {
    89  				if !strings.Contains(got, want) {
    90  					t.Errorf(`"%s" wasn't found in "%s"'`, want, got)
    91  				}
    92  			}
    93  		})
    94  	}
    95  }
    96  
    97  func staticFileProvider(t *testing.T) covermerger.FileVersProvider {
    98  	m := mergermocks.NewFileVersProvider(t)
    99  	m.On("GetFileVersions", mock.Anything, mock.Anything).
   100  		Return(func(targetFilePath string, repoCommits ...covermerger.RepoCommit,
   101  		) covermerger.FileVersions {
   102  			res := covermerger.FileVersions{}
   103  			for _, rc := range repoCommits {
   104  				res[rc] = `line1
   105  line2
   106  line3`
   107  			}
   108  			return res
   109  		}, nil)
   110  	return m
   111  }
   112  
   113  func emptyCoverageDBFixture(t *testing.T, times int) spannerclient.SpannerClient {
   114  	mRowIterator := mocks.NewRowIterator(t)
   115  	mRowIterator.On("Stop").Return().Times(times)
   116  	mRowIterator.On("Next").
   117  		Return(nil, iterator.Done).Times(times)
   118  
   119  	mTran := mocks.NewReadOnlyTransaction(t)
   120  	mTran.On("Query", mock.Anything, mock.Anything).
   121  		Return(mRowIterator).Times(times)
   122  
   123  	m := mocks.NewSpannerClient(t)
   124  	m.On("Single").
   125  		Return(mTran).Times(times)
   126  	return m
   127  }
   128  
   129  func coverageDBFixture(t *testing.T) spannerclient.SpannerClient {
   130  	mRowIt := newRowIteratorMock(t, []*coveragedb.LinesCoverage{{
   131  		LinesInstrumented: []int64{1, 2, 3},
   132  		HitCounts:         []int64{4, 5, 6},
   133  	}})
   134  
   135  	mTran := mocks.NewReadOnlyTransaction(t)
   136  	mTran.On("Query", mock.Anything, mock.Anything).
   137  		Return(mRowIt).Once()
   138  
   139  	m := mocks.NewSpannerClient(t)
   140  	m.On("Single").
   141  		Return(mTran).Once()
   142  	return m
   143  }
   144  
   145  func multiManagerCovDBFixture(t *testing.T) spannerclient.SpannerClient {
   146  	mReadFullCoverageTran := mocks.NewReadOnlyTransaction(t)
   147  	mReadFullCoverageTran.On("Query", mock.Anything, mock.Anything).
   148  		Return(newRowIteratorMock(t, []*coveragedb.LinesCoverage{{
   149  			LinesInstrumented: []int64{1, 2, 3},
   150  			HitCounts:         []int64{4, 5, 6},
   151  		}})).Once()
   152  
   153  	mReadPartialCoverageTran := mocks.NewReadOnlyTransaction(t)
   154  	mReadPartialCoverageTran.On("Query", mock.Anything, mock.Anything).
   155  		Return(newRowIteratorMock(t, []*coveragedb.LinesCoverage{{
   156  			LinesInstrumented: []int64{1, 2},
   157  			HitCounts:         []int64{3, 5},
   158  		}})).Once()
   159  
   160  	m := mocks.NewSpannerClient(t)
   161  	// The order matters. Full coverage is fetched second.
   162  	m.On("Single").
   163  		Return(mReadPartialCoverageTran).Once()
   164  	m.On("Single").
   165  		Return(mReadFullCoverageTran).Once()
   166  
   167  	return m
   168  }
   169  
   170  func newRowIteratorMock[K any](t *testing.T, cov []*K,
   171  ) *mocks.RowIterator {
   172  	m := mocks.NewRowIterator(t)
   173  	m.On("Stop").Once().Return()
   174  	for _, item := range cov {
   175  		mRow := mocks.NewRow(t)
   176  		mRow.On("ToStruct", mock.Anything).
   177  			Run(func(args mock.Arguments) {
   178  				arg := args.Get(0).(*K)
   179  				*arg = *item
   180  			}).
   181  			Return(nil).Once()
   182  
   183  		m.On("Next").
   184  			Return(mRow, nil).Once()
   185  	}
   186  
   187  	m.On("Next").
   188  		Return(nil, iterator.Done).Once()
   189  	return m
   190  }