1package main 2 3import ( 4 "encoding/json" 5 "flag" 6 "fmt" 7 "log" 8 9 "go/ast" 10 "go/parser" 11 "go/token" 12 13 "golang.org/x/tools/cover" 14) 15 16type CoverageTotal struct { 17 Count int `json:"count"` 18 Covered int `json:"covered"` 19 Uncovered int `json:"notcovered"` 20 Percent float64 `json:"percent"` 21} 22 23type CoverageTotals struct { 24 Functions CoverageTotal `json:"functions,omitempty"` 25 Lines CoverageTotal `json:"lines,omitempty"` 26 Regions CoverageTotal `json:"regions,omitempty"` 27 Instantiations CoverageTotal `json:"instantiations,omitempty"` 28 Branches CoverageTotal `json:"branches,omitempty"` 29} 30 31type CoverageFile struct { 32 Summary CoverageTotals `json:"summary,omitempty"` 33 Filename string `json:"filename,omitempty"` 34} 35 36type CoverageData struct { 37 Totals CoverageTotals `json:"totals,omitempty"` 38 Files []CoverageFile `json:"files,omitempty"` 39} 40 41type PositionInterval struct { 42 start token.Position 43 end token.Position 44} 45 46type CoverageSummary struct { 47 Data []CoverageData `json:"data,omitempty"` 48 Type string `json:"type,omitempty"` 49 Version string `json:"version,omitempty"` 50} 51 52func isFunctionCovered(s token.Position, e token.Position, blocks []cover.ProfileBlock) bool { 53 for _, b := range blocks { 54 if b.StartLine >= s.Line && b.StartLine <= e.Line && b.EndLine >= s.Line && b.EndLine <= e.Line { 55 if b.Count > 0 { 56 return true 57 } 58 } 59 } 60 return false 61} 62 63func computePercent(s *CoverageTotals) { 64 s.Regions.Percent = float64(100*s.Regions.Covered) / float64(s.Regions.Count) 65 s.Lines.Percent = float64(100*s.Lines.Covered) / float64(s.Lines.Count) 66 s.Functions.Percent = float64(100*s.Functions.Covered) / float64(s.Functions.Count) 67} 68 69func main() { 70 flag.Parse() 71 72 if len(flag.Args()) != 1 { 73 log.Fatalf("needs exactly one argument") 74 } 75 profiles, err := cover.ParseProfiles(flag.Args()[0]) 76 if err != nil { 77 log.Fatalf("failed to parse profiles: %v", err) 78 } 79 r := CoverageSummary{} 80 r.Type = "oss-fuzz.go.coverage.json.export" 81 r.Version = "2.0.1" 82 r.Data = make([]CoverageData, 1) 83 for _, p := range profiles { 84 fset := token.NewFileSet() // positions are relative to fset 85 f, err := parser.ParseFile(fset, p.FileName, nil, 0) 86 if err != nil { 87 panic(err) 88 } 89 fileCov := CoverageFile{} 90 fileCov.Filename = p.FileName 91 ast.Inspect(f, func(n ast.Node) bool { 92 switch x := n.(type) { 93 case *ast.FuncLit: 94 startf := fset.Position(x.Pos()) 95 endf := fset.Position(x.End()) 96 fileCov.Summary.Functions.Count++ 97 if isFunctionCovered(startf, endf, p.Blocks) { 98 fileCov.Summary.Functions.Covered++ 99 } else { 100 fileCov.Summary.Functions.Uncovered++ 101 } 102 case *ast.FuncDecl: 103 startf := fset.Position(x.Pos()) 104 endf := fset.Position(x.End()) 105 fileCov.Summary.Functions.Count++ 106 if isFunctionCovered(startf, endf, p.Blocks) { 107 fileCov.Summary.Functions.Covered++ 108 } else { 109 fileCov.Summary.Functions.Uncovered++ 110 } 111 } 112 return true 113 }) 114 115 for _, b := range p.Blocks { 116 fileCov.Summary.Regions.Count++ 117 if b.Count > 0 { 118 fileCov.Summary.Regions.Covered++ 119 } else { 120 fileCov.Summary.Regions.Uncovered++ 121 } 122 123 fileCov.Summary.Lines.Count += b.NumStmt 124 if b.Count > 0 { 125 fileCov.Summary.Lines.Covered += b.NumStmt 126 } else { 127 fileCov.Summary.Lines.Uncovered += b.NumStmt 128 } 129 } 130 r.Data[0].Totals.Regions.Count += fileCov.Summary.Regions.Count 131 r.Data[0].Totals.Regions.Covered += fileCov.Summary.Regions.Covered 132 r.Data[0].Totals.Regions.Uncovered += fileCov.Summary.Regions.Uncovered 133 r.Data[0].Totals.Lines.Count += fileCov.Summary.Lines.Count 134 r.Data[0].Totals.Lines.Covered += fileCov.Summary.Lines.Covered 135 r.Data[0].Totals.Lines.Uncovered += fileCov.Summary.Lines.Uncovered 136 r.Data[0].Totals.Functions.Count += fileCov.Summary.Functions.Count 137 r.Data[0].Totals.Functions.Covered += fileCov.Summary.Functions.Covered 138 r.Data[0].Totals.Functions.Uncovered += fileCov.Summary.Functions.Uncovered 139 140 computePercent(&fileCov.Summary) 141 r.Data[0].Files = append(r.Data[0].Files, fileCov) 142 } 143 144 computePercent(&r.Data[0].Totals) 145 o, _ := json.Marshal(r) 146 fmt.Printf(string(o)) 147} 148