package main import ( "encoding/json" "flag" "fmt" "log" "go/ast" "go/parser" "go/token" "golang.org/x/tools/cover" ) type CoverageTotal struct { Count int `json:"count"` Covered int `json:"covered"` Uncovered int `json:"notcovered"` Percent float64 `json:"percent"` } type CoverageTotals struct { Functions CoverageTotal `json:"functions,omitempty"` Lines CoverageTotal `json:"lines,omitempty"` Regions CoverageTotal `json:"regions,omitempty"` Instantiations CoverageTotal `json:"instantiations,omitempty"` Branches CoverageTotal `json:"branches,omitempty"` } type CoverageFile struct { Summary CoverageTotals `json:"summary,omitempty"` Filename string `json:"filename,omitempty"` } type CoverageData struct { Totals CoverageTotals `json:"totals,omitempty"` Files []CoverageFile `json:"files,omitempty"` } type PositionInterval struct { start token.Position end token.Position } type CoverageSummary struct { Data []CoverageData `json:"data,omitempty"` Type string `json:"type,omitempty"` Version string `json:"version,omitempty"` } func isFunctionCovered(s token.Position, e token.Position, blocks []cover.ProfileBlock) bool { for _, b := range blocks { if b.StartLine >= s.Line && b.StartLine <= e.Line && b.EndLine >= s.Line && b.EndLine <= e.Line { if b.Count > 0 { return true } } } return false } func computePercent(s *CoverageTotals) { s.Regions.Percent = float64(100*s.Regions.Covered) / float64(s.Regions.Count) s.Lines.Percent = float64(100*s.Lines.Covered) / float64(s.Lines.Count) s.Functions.Percent = float64(100*s.Functions.Covered) / float64(s.Functions.Count) } func main() { flag.Parse() if len(flag.Args()) != 1 { log.Fatalf("needs exactly one argument") } profiles, err := cover.ParseProfiles(flag.Args()[0]) if err != nil { log.Fatalf("failed to parse profiles: %v", err) } r := CoverageSummary{} r.Type = "oss-fuzz.go.coverage.json.export" r.Version = "2.0.1" r.Data = make([]CoverageData, 1) for _, p := range profiles { fset := token.NewFileSet() // positions are relative to fset f, err := parser.ParseFile(fset, p.FileName, nil, 0) if err != nil { panic(err) } fileCov := CoverageFile{} fileCov.Filename = p.FileName ast.Inspect(f, func(n ast.Node) bool { switch x := n.(type) { case *ast.FuncLit: startf := fset.Position(x.Pos()) endf := fset.Position(x.End()) fileCov.Summary.Functions.Count++ if isFunctionCovered(startf, endf, p.Blocks) { fileCov.Summary.Functions.Covered++ } else { fileCov.Summary.Functions.Uncovered++ } case *ast.FuncDecl: startf := fset.Position(x.Pos()) endf := fset.Position(x.End()) fileCov.Summary.Functions.Count++ if isFunctionCovered(startf, endf, p.Blocks) { fileCov.Summary.Functions.Covered++ } else { fileCov.Summary.Functions.Uncovered++ } } return true }) for _, b := range p.Blocks { fileCov.Summary.Regions.Count++ if b.Count > 0 { fileCov.Summary.Regions.Covered++ } else { fileCov.Summary.Regions.Uncovered++ } fileCov.Summary.Lines.Count += b.NumStmt if b.Count > 0 { fileCov.Summary.Lines.Covered += b.NumStmt } else { fileCov.Summary.Lines.Uncovered += b.NumStmt } } r.Data[0].Totals.Regions.Count += fileCov.Summary.Regions.Count r.Data[0].Totals.Regions.Covered += fileCov.Summary.Regions.Covered r.Data[0].Totals.Regions.Uncovered += fileCov.Summary.Regions.Uncovered r.Data[0].Totals.Lines.Count += fileCov.Summary.Lines.Count r.Data[0].Totals.Lines.Covered += fileCov.Summary.Lines.Covered r.Data[0].Totals.Lines.Uncovered += fileCov.Summary.Lines.Uncovered r.Data[0].Totals.Functions.Count += fileCov.Summary.Functions.Count r.Data[0].Totals.Functions.Covered += fileCov.Summary.Functions.Covered r.Data[0].Totals.Functions.Uncovered += fileCov.Summary.Functions.Uncovered computePercent(&fileCov.Summary) r.Data[0].Files = append(r.Data[0].Files, fileCov) } computePercent(&r.Data[0].Totals) o, _ := json.Marshal(r) fmt.Printf(string(o)) }