github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/coveragedb/coveragedb_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 coveragedb
     5  
     6  import (
     7  	"context"
     8  	"testing"
     9  
    10  	"cloud.google.com/go/civil"
    11  	"github.com/google/syzkaller/pkg/coveragedb/mocks"
    12  	"github.com/google/syzkaller/pkg/coveragedb/spannerclient"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/mock"
    15  	"google.golang.org/api/iterator"
    16  )
    17  
    18  func TestFilesCoverageWithDetails(t *testing.T) {
    19  	period, _ := MakeTimePeriod(
    20  		civil.Date{Year: 2025, Month: 1, Day: 1},
    21  		"day")
    22  	tests := []struct {
    23  		name       string
    24  		scope      *SelectScope
    25  		client     func() spannerclient.SpannerClient
    26  		onlyUnique bool
    27  		want       []*FileCoverageWithDetails
    28  		wantErr    bool
    29  	}{
    30  		{
    31  			name:    "empty scope",
    32  			scope:   &SelectScope{},
    33  			want:    nil,
    34  			wantErr: false,
    35  		},
    36  		{
    37  			name: "single day, no filters, empty DB => no coverage",
    38  			scope: &SelectScope{
    39  				Ns:      "upstream",
    40  				Periods: []TimePeriod{period},
    41  			},
    42  			client:  func() spannerclient.SpannerClient { return emptyCoverageDBFixture(t, 1) },
    43  			want:    nil,
    44  			wantErr: false,
    45  		},
    46  		{
    47  			name: "single day, unique coverage, empty DB => no coverage",
    48  			scope: &SelectScope{
    49  				Ns:      "upstream",
    50  				Periods: []TimePeriod{period},
    51  			},
    52  			client:     func() spannerclient.SpannerClient { return emptyCoverageDBFixture(t, 2) },
    53  			onlyUnique: true,
    54  			want:       nil,
    55  			wantErr:    false,
    56  		},
    57  		{
    58  			name: "single day, unique coverage, empty partial result => 0/3 covered",
    59  			scope: &SelectScope{
    60  				Ns:      "upstream",
    61  				Periods: []TimePeriod{period},
    62  			},
    63  			client: func() spannerclient.SpannerClient {
    64  				return fullCoverageDBFixture(
    65  					t,
    66  					[]*FileCoverageWithLineInfo{
    67  						{
    68  							FileCoverageWithDetails: FileCoverageWithDetails{
    69  								Filepath:     "file1",
    70  								Instrumented: 3,
    71  								Covered:      3,
    72  							},
    73  							LinesInstrumented: []int64{1, 2, 3},
    74  							HitCounts:         []int64{1, 1, 1},
    75  						},
    76  					},
    77  					nil)
    78  			},
    79  			onlyUnique: true,
    80  			want: []*FileCoverageWithDetails{
    81  				{
    82  					Filepath:     "file1",
    83  					Instrumented: 3,
    84  					Covered:      0,
    85  					TimePeriod:   period,
    86  				},
    87  			},
    88  			wantErr: false,
    89  		},
    90  		{
    91  			name: "single day, unique coverage, full result match => 3/3 covered",
    92  			scope: &SelectScope{
    93  				Ns:      "upstream",
    94  				Periods: []TimePeriod{period},
    95  			},
    96  			client: func() spannerclient.SpannerClient {
    97  				return fullCoverageDBFixture(
    98  					t,
    99  					[]*FileCoverageWithLineInfo{
   100  						{
   101  							FileCoverageWithDetails: FileCoverageWithDetails{
   102  								Filepath:     "file1",
   103  								Instrumented: 3,
   104  								Covered:      3,
   105  							},
   106  							LinesInstrumented: []int64{1, 2, 3},
   107  							HitCounts:         []int64{1, 1, 1},
   108  						},
   109  					},
   110  					[]*FileCoverageWithLineInfo{
   111  						{
   112  							FileCoverageWithDetails: FileCoverageWithDetails{
   113  								Filepath:     "file1",
   114  								Instrumented: 3,
   115  								Covered:      3,
   116  							},
   117  							LinesInstrumented: []int64{1, 2, 3},
   118  							HitCounts:         []int64{1, 1, 1},
   119  						},
   120  					})
   121  			},
   122  			onlyUnique: true,
   123  			want: []*FileCoverageWithDetails{
   124  				{
   125  					Filepath:     "file1",
   126  					Instrumented: 3,
   127  					Covered:      3,
   128  					TimePeriod:   period,
   129  				},
   130  			},
   131  			wantErr: false,
   132  		},
   133  		{
   134  			name: "single day, unique coverage, partial result match => 3/5 covered",
   135  			scope: &SelectScope{
   136  				Ns:      "upstream",
   137  				Periods: []TimePeriod{period},
   138  			},
   139  			client: func() spannerclient.SpannerClient {
   140  				return fullCoverageDBFixture(
   141  					t,
   142  					[]*FileCoverageWithLineInfo{
   143  						{
   144  							FileCoverageWithDetails: FileCoverageWithDetails{
   145  								Filepath:     "file1",
   146  								Instrumented: 5,
   147  								Covered:      5,
   148  							},
   149  							LinesInstrumented: []int64{1, 2, 3, 4, 5},
   150  							HitCounts:         []int64{3, 4, 5, 6, 7},
   151  						},
   152  					},
   153  					[]*FileCoverageWithLineInfo{
   154  						{
   155  							FileCoverageWithDetails: FileCoverageWithDetails{
   156  								Filepath:     "file1",
   157  								Instrumented: 4,
   158  								Covered:      3,
   159  							},
   160  							LinesInstrumented: []int64{1, 2, 3, 5},
   161  							HitCounts:         []int64{3, 0, 5, 7},
   162  						},
   163  					})
   164  			},
   165  			onlyUnique: true,
   166  			want: []*FileCoverageWithDetails{
   167  				{
   168  					Filepath:     "file1",
   169  					Instrumented: 5,
   170  					Covered:      3,
   171  					TimePeriod:   period,
   172  				},
   173  			},
   174  			wantErr: false,
   175  		},
   176  	}
   177  	for _, test := range tests {
   178  		t.Run(test.name, func(t *testing.T) {
   179  			var testClient spannerclient.SpannerClient
   180  			if test.client != nil {
   181  				testClient = test.client()
   182  			}
   183  			got, gotErr := FilesCoverageWithDetails(
   184  				context.Background(),
   185  				testClient, test.scope, test.onlyUnique)
   186  			if test.wantErr {
   187  				assert.Error(t, gotErr)
   188  			} else {
   189  				assert.NoError(t, gotErr)
   190  			}
   191  			assert.Equal(t, test.want, got)
   192  		})
   193  	}
   194  }
   195  
   196  func emptyCoverageDBFixture(t *testing.T, times int) spannerclient.SpannerClient {
   197  	mRowIterator := mocks.NewRowIterator(t)
   198  	mRowIterator.On("Stop").Return().Times(times)
   199  	mRowIterator.On("Next").
   200  		Return(nil, iterator.Done).Times(times)
   201  
   202  	mTran := mocks.NewReadOnlyTransaction(t)
   203  	mTran.On("Query", mock.Anything, mock.Anything).
   204  		Return(mRowIterator).Times(times)
   205  
   206  	m := mocks.NewSpannerClient(t)
   207  	m.On("Single").
   208  		Return(mTran).Times(times)
   209  	return m
   210  }
   211  
   212  func fullCoverageDBFixture(
   213  	t *testing.T, full, partial []*FileCoverageWithLineInfo,
   214  ) spannerclient.SpannerClient {
   215  	mPartialTran := mocks.NewReadOnlyTransaction(t)
   216  	mPartialTran.On("Query", mock.Anything, mock.Anything).
   217  		Return(newRowIteratorMock(t, partial)).Once()
   218  
   219  	mFullTran := mocks.NewReadOnlyTransaction(t)
   220  	mFullTran.On("Query", mock.Anything, mock.Anything).
   221  		Return(newRowIteratorMock(t, full)).Once()
   222  
   223  	m := mocks.NewSpannerClient(t)
   224  	m.On("Single").
   225  		Return(mPartialTran).Once()
   226  	m.On("Single").
   227  		Return(mFullTran).Once()
   228  	return m
   229  }
   230  
   231  func newRowIteratorMock(t *testing.T, events []*FileCoverageWithLineInfo,
   232  ) *mocks.RowIterator {
   233  	m := mocks.NewRowIterator(t)
   234  	m.On("Stop").Once().Return()
   235  	for _, item := range events {
   236  		mRow := mocks.NewRow(t)
   237  		mRow.On("ToStruct", mock.Anything).
   238  			Run(func(args mock.Arguments) {
   239  				arg := args.Get(0).(*FileCoverageWithLineInfo)
   240  				*arg = *item
   241  			}).
   242  			Return(nil).Once()
   243  
   244  		m.On("Next").
   245  			Return(mRow, nil).Once()
   246  	}
   247  
   248  	m.On("Next").
   249  		Return(nil, iterator.Done).Once()
   250  	return m
   251  }