Skip to content

Commit 136953f

Browse files
committed
pkg/coveragedb: store information about covered file functions in db
1 parent f6dd7a7 commit 136953f

File tree

4 files changed

+83
-46
lines changed

4 files changed

+83
-46
lines changed

dashboard/app/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1936,7 +1936,7 @@ func apiCreateUploadURL(c context.Context, payload io.Reader) (interface{}, erro
19361936

19371937
// apiSaveCoverage reads jsonl data from payload and stores it to coveragedb.
19381938
// First payload jsonl line is a coveragedb.HistoryRecord (w/o session and time).
1939-
// Second+ records are coveragedb.MergedCoverageRecord.
1939+
// Second+ records are coveragedb.JSONLWrapper.
19401940
func apiSaveCoverage(c context.Context, payload io.Reader) (interface{}, error) {
19411941
descr := new(coveragedb.HistoryRecord)
19421942
jsonDec := json.NewDecoder(payload)

pkg/coveragedb/coveragedb.go

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,6 @@ import (
2222
"google.golang.org/api/iterator"
2323
)
2424

25-
type FilesRecord struct {
26-
Session string
27-
FilePath string
28-
Instrumented int64
29-
Covered int64
30-
LinesInstrumented []int64
31-
HitCounts []int64
32-
Manager string // "*" means "collected from all managers"
33-
}
34-
35-
type FileSubsystems struct {
36-
Namespace string
37-
FilePath string
38-
Subsystems []string
39-
}
40-
4125
type HistoryRecord struct {
4226
Session string
4327
Time time.Time
@@ -49,6 +33,25 @@ type HistoryRecord struct {
4933
TotalRows int64
5034
}
5135

36+
type MergedCoverageRecord struct {
37+
Manager string
38+
FilePath string
39+
FileData *Coverage
40+
}
41+
42+
// FuncLines represents the 'functions' table records.
43+
// It could be used to maps 'hitcounts' from 'files' table to the function names.
44+
type FuncLines struct {
45+
FilePath string
46+
FuncName string
47+
Lines []int64 // List of lines we know belong to this function name according to the addr2line output.
48+
}
49+
50+
type JSONLWrapper struct {
51+
MCR *MergedCoverageRecord
52+
FL *FuncLines
53+
}
54+
5255
type Coverage struct {
5356
Instrumented int64
5457
Covered int64
@@ -65,23 +68,27 @@ func (c *Coverage) AddLineHitCount(line int, hitCount int64) {
6568
}
6669
}
6770

68-
type MergedCoverageRecord struct {
69-
Manager string
70-
FilePath string
71-
FileData *Coverage
71+
type filesRecord struct {
72+
Session string
73+
FilePath string
74+
Instrumented int64
75+
Covered int64
76+
LinesInstrumented []int64
77+
HitCounts []int64
78+
Manager string // "*" means "collected from all managers"
7279
}
7380

74-
// FuncLines represents the 'functions' table records.
75-
// It could be used to maps 'hitcounts' from 'files' table to the function names.
76-
type FuncLines struct {
81+
type functionsRecord struct {
82+
Session string
7783
FilePath string
7884
FuncName string
79-
Lines []int64 // List of lines we know belong to this function name according to the addr2line output.
85+
Lines []int64
8086
}
8187

82-
type JSONLWrapper struct {
83-
MCR *MergedCoverageRecord
84-
FL *FuncLines
88+
type fileSubsystems struct {
89+
Namespace string
90+
FilePath string
91+
Subsystems []string
8592
}
8693

8794
func SaveMergeResult(ctx context.Context, client spannerclient.SpannerClient, descr *HistoryRecord, dec *json.Decoder,
@@ -94,23 +101,26 @@ func SaveMergeResult(ctx context.Context, client spannerclient.SpannerClient, de
94101
ssCache := make(map[string][]string)
95102

96103
session := uuid.New().String()
97-
mutations := []*spanner.Mutation{}
104+
var mutations []*spanner.Mutation
98105

99106
for {
100-
var mcr MergedCoverageRecord
101-
err := dec.Decode(&mcr)
107+
var wr JSONLWrapper
108+
err := dec.Decode(&wr)
102109
if err == io.EOF {
103110
break
104111
}
105112
if err != nil {
106113
return rowsCreated, fmt.Errorf("dec.Decode(MergedCoverageRecord): %w", err)
107114
}
108-
if mcr.FileData == nil {
109-
return rowsCreated, errors.New("field MergedCoverageRecord.FileData can't be nil")
115+
if mcr := wr.MCR; mcr != nil {
116+
mutations = append(mutations, fileRecordMutation(session, mcr))
117+
subsystems := getFileSubsystems(mcr.FilePath, ssMatcher, ssCache)
118+
mutations = append(mutations, fileSubsystemsMutation(descr.Namespace, mcr.FilePath, subsystems))
119+
} else if fl := wr.FL; fl != nil {
120+
mutations = append(mutations, fileFunctionsMutation(session, fl))
121+
} else {
122+
return rowsCreated, errors.New("JSONLWrapper can't be empty")
110123
}
111-
mutations = append(mutations, fileRecordMutation(session, &mcr))
112-
subsystems := fileSubsystems(mcr.FilePath, ssMatcher, ssCache)
113-
mutations = append(mutations, fileSubsystemsMutation(descr.Namespace, mcr.FilePath, subsystems))
114124
// There is a limit on the number of mutations per transaction (80k) imposed by the DB.
115125
// This includes both explicit mutations of the fields (6 fields * 1k records = 6k mutations)
116126
// and implicit index mutations.
@@ -203,8 +213,21 @@ func historyMutation(session string, template *HistoryRecord) *spanner.Mutation
203213
return historyInsert
204214
}
205215

216+
func fileFunctionsMutation(session string, fl *FuncLines) *spanner.Mutation {
217+
insert, err := spanner.InsertOrUpdateStruct("functions", &functionsRecord{
218+
Session: session,
219+
FilePath: fl.FilePath,
220+
FuncName: fl.FuncName,
221+
Lines: fl.Lines,
222+
})
223+
if err != nil {
224+
panic(fmt.Sprintf("failed to fileFunctionsMutation: %v", err))
225+
}
226+
return insert
227+
}
228+
206229
func fileRecordMutation(session string, mcr *MergedCoverageRecord) *spanner.Mutation {
207-
insert, err := spanner.InsertOrUpdateStruct("files", &FilesRecord{
230+
insert, err := spanner.InsertOrUpdateStruct("files", &filesRecord{
208231
Session: session,
209232
FilePath: mcr.FilePath,
210233
Instrumented: mcr.FileData.Instrumented,
@@ -220,7 +243,7 @@ func fileRecordMutation(session string, mcr *MergedCoverageRecord) *spanner.Muta
220243
}
221244

222245
func fileSubsystemsMutation(ns, filePath string, subsystems []string) *spanner.Mutation {
223-
insert, err := spanner.InsertOrUpdateStruct("file_subsystems", &FileSubsystems{
246+
insert, err := spanner.InsertOrUpdateStruct("file_subsystems", &fileSubsystems{
224247
Namespace: ns,
225248
FilePath: filePath,
226249
Subsystems: subsystems,
@@ -231,7 +254,7 @@ func fileSubsystemsMutation(ns, filePath string, subsystems []string) *spanner.M
231254
return insert
232255
}
233256

234-
func fileSubsystems(filePath string, ssMatcher *subsystem.PathMatcher, ssCache map[string][]string) []string {
257+
func getFileSubsystems(filePath string, ssMatcher *subsystem.PathMatcher, ssCache map[string][]string) []string {
235258
sss, cached := ssCache[filePath]
236259
if !cached {
237260
for _, match := range ssMatcher.Match(filePath) {

pkg/coveragedb/coveragedb_mock_test.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,10 @@ func TestSaveMergeResult(t *testing.T) {
4545
jsonl: strings.NewReader(`{a}`),
4646
wantErr: true,
4747
},
48+
// nolint: dupl
4849
{
49-
name: "1 record, Ok",
50-
jsonl: strings.NewReader(`{"FileData":{}}`),
50+
name: "1 MCR record, Ok",
51+
jsonl: strings.NewReader(`{"MCR":{"FileData":{}}}`),
5152
descr: &HistoryRecord{},
5253
wantRows: 3, // 1 in files, 1 in file_subsystems and 1 in merge_history
5354
mockTune: func(t *testing.T, m *mocks.SpannerClient) {
@@ -57,10 +58,23 @@ func TestSaveMergeResult(t *testing.T) {
5758
Once()
5859
},
5960
},
61+
// nolint: dupl
62+
{
63+
name: "1 FC record, Ok",
64+
jsonl: strings.NewReader(`{"FL":{}}`),
65+
descr: &HistoryRecord{},
66+
wantRows: 2, // 1 in functions and 1 in merge_history
67+
mockTune: func(t *testing.T, m *mocks.SpannerClient) {
68+
m.
69+
On("Apply", mock.Anything, mock.Anything).
70+
Return(time.Now(), nil).
71+
Once()
72+
},
73+
},
6074
{
6175
name: "2 records, Ok",
62-
jsonl: strings.NewReader(` {"FileData":{}}
63-
{"FileData":{}}`),
76+
jsonl: strings.NewReader(` {"MCR":{"FileData":{}}}
77+
{"MCR":{"FileData":{}}}`),
6478
descr: &HistoryRecord{},
6579
wantRows: 5,
6680
mockTune: func(t *testing.T, m *mocks.SpannerClient) {
@@ -77,7 +91,7 @@ func TestSaveMergeResult(t *testing.T) {
7791
},
7892
{
7993
name: "2k records, Ok",
80-
jsonl: strings.NewReader(strings.Repeat("{\"FileData\":{}}\n", 2000)),
94+
jsonl: strings.NewReader(strings.Repeat("{\"MCR\":{\"FileData\":{}}}\n", 2000)),
8195
descr: &HistoryRecord{},
8296
wantRows: 4001,
8397
mockTune: func(t *testing.T, m *mocks.SpannerClient) {

pkg/covermerger/covermerger_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ func TestMergeCSVWriteJSONL_and_coveragedb_SaveMergeResult(t *testing.T) {
5555
spannerMock := mocks.NewSpannerClient(t)
5656
spannerMock.
5757
On("Apply", mock.Anything, mock.MatchedBy(func(ms []*spanner.Mutation) bool {
58-
// 1 file * (5 managers + 1 manager total) x 2 (to update files and subsystems) + 1 merge_history
59-
return len(ms) == 13
58+
// 1 file * (5 managers + 1 manager total) x 2 (to update files and subsystems) + 1 merge_history + 18 functions
59+
return len(ms) == 13+18
6060
})).
6161
Return(time.Now(), nil).
6262
Once()

0 commit comments

Comments
 (0)