github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/cover/file.go (about) 1 // Copyright 2024 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 cover 5 6 import ( 7 "context" 8 "fmt" 9 "html" 10 "strings" 11 12 "github.com/google/syzkaller/pkg/coveragedb" 13 "github.com/google/syzkaller/pkg/covermerger" 14 ) 15 16 type lineRender func(string, int, *covermerger.MergeResult, *CoverageRenderConfig) string 17 18 type CoverageRenderConfig struct { 19 RendLine lineRender 20 ShowLineCoverage bool 21 ShowLineNumbers bool 22 ShowLineSourceExplanation bool 23 } 24 25 func DefaultTextRenderConfig() *CoverageRenderConfig { 26 return &CoverageRenderConfig{ 27 RendLine: RendTextLine, 28 ShowLineCoverage: true, 29 ShowLineNumbers: true, 30 ShowLineSourceExplanation: false, 31 } 32 } 33 34 func DefaultHTMLRenderConfig() *CoverageRenderConfig { 35 return &CoverageRenderConfig{ 36 RendLine: RendHTMLLine, 37 ShowLineCoverage: true, 38 ShowLineNumbers: true, 39 ShowLineSourceExplanation: false, 40 } 41 } 42 43 func RendFileCoverage(repo, forCommit, filePath string, fileProvider covermerger.FileVersProvider, 44 mr *covermerger.MergeResult, renderConfig *CoverageRenderConfig) (string, error) { 45 repoCommit := covermerger.RepoCommit{Repo: repo, Commit: forCommit} 46 files, err := fileProvider.GetFileVersions(filePath, repoCommit) 47 if err != nil { 48 return "", fmt.Errorf("failed to GetFileVersions: %w", err) 49 } 50 return rendResult(files[repoCommit], mr, renderConfig), nil 51 } 52 53 // nolint:revive 54 func GetMergeResult(c context.Context, ns, repo, forCommit, sourceCommit, filePath string, 55 proxy covermerger.FuncProxyURI, tp coveragedb.TimePeriod) (*covermerger.MergeResult, error) { 56 config := &covermerger.Config{ 57 Jobs: 1, 58 Base: covermerger.RepoCommit{ 59 Repo: repo, 60 Commit: forCommit, 61 }, 62 FileVersProvider: covermerger.MakeWebGit(proxy), 63 } 64 65 fromDate, toDate := tp.DatesFromTo() 66 csvReader, err := covermerger.InitNsRecords(c, ns, filePath, sourceCommit, fromDate, toDate) 67 if err != nil { 68 return nil, fmt.Errorf("failed to covermerger.InitNsRecords: %w", err) 69 } 70 defer csvReader.Close() 71 72 ch := make(chan *covermerger.FileMergeResult, 1) 73 if err := covermerger.MergeCSVData(c, config, csvReader, ch); err != nil { 74 return nil, fmt.Errorf("error merging coverage: %w", err) 75 } 76 77 var mr *covermerger.MergeResult 78 select { 79 case fmr := <-ch: 80 if fmr != nil { 81 mr = fmr.MergeResult 82 } 83 default: 84 } 85 86 if mr != nil { 87 return nil, fmt.Errorf("no merge result for file %s", filePath) 88 } 89 return mr, nil 90 } 91 92 func rendResult(content string, coverage *covermerger.MergeResult, renderConfig *CoverageRenderConfig) string { 93 if coverage == nil { 94 coverage = &covermerger.MergeResult{ 95 HitCounts: map[int]int64{}, 96 LineDetails: map[int][]*covermerger.FileRecord{}, 97 } 98 } 99 srcLines := strings.Split(content, "\n") 100 var resLines []string 101 for i, srcLine := range srcLines { 102 resLines = append(resLines, renderConfig.RendLine(srcLine, i+1, coverage, renderConfig)) 103 } 104 return strings.Join(resLines, "\n") 105 } 106 107 func RendTextLine(code string, line int, coverage *covermerger.MergeResult, config *CoverageRenderConfig) string { 108 res := "" 109 if config.ShowLineSourceExplanation { 110 explanation := "" 111 lineDetails, exist := coverage.LineDetails[line] 112 if exist { 113 explanation = fmt.Sprintf("(%d)%s ", len(lineDetails), mainSignalSource(lineDetails)) 114 } 115 res += fmt.Sprintf("%50s", explanation) 116 } 117 covered, instrumented := coverage.HitCounts[line] 118 if config.ShowLineCoverage { 119 covStr := fmt.Sprintf("%6d", covered) 120 if !instrumented { 121 covStr = strings.Repeat(" ", 6) 122 } 123 res += fmt.Sprintf("%s ", covStr) 124 } 125 if config.ShowLineNumbers { 126 res += fmt.Sprintf("%6d ", line) 127 } 128 res += code 129 return res 130 } 131 132 func RendHTMLLine(code string, line int, coverage *covermerger.MergeResult, config *CoverageRenderConfig) string { 133 textLine := RendTextLine(code, line, coverage, config) 134 return `<pre style="margin: 0">` + html.EscapeString(textLine) + "</pre>" 135 } 136 137 func mainSignalSource(sources []*covermerger.FileRecord) string { 138 res := "" 139 prevMax := -1 140 for _, source := range sources { 141 if source.HitCount > prevMax { 142 prevMax = source.HitCount 143 res = fmt.Sprintf("%s:%d", source.Commit, source.StartLine) 144 } 145 } 146 return res 147 }