aboutsummaryrefslogtreecommitdiff
path: root/infra/go/coverage/gocovsum/gocovsum.go
blob: 20660061975e9d4f742922e230a355e5572c1324 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"

	"go/ast"
	"go/parser"
	"go/token"
	"os"
	"path"

	"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"`
}

type CoverageData struct {
	Totals CoverageTotals `json:"totals,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 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 = "1.0.0"
	r.Data = make([]CoverageData, 1)
	gopath := os.Getenv("GOPATH")
	if len(gopath) == 0 {
		gopath = os.Getenv("HOME") + "/go"
	}
	for _, p := range profiles {
		fset := token.NewFileSet() // positions are relative to fset
		f, err := parser.ParseFile(fset, path.Join(gopath, "src", p.FileName), nil, 0)
		if err != nil {
			panic(err)
		}
		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())
				r.Data[0].Totals.Functions.Count++
				if isFunctionCovered(startf, endf, p.Blocks) {
					r.Data[0].Totals.Functions.Covered++
				} else {
					r.Data[0].Totals.Functions.Uncovered++
				}
			case *ast.FuncDecl:
				startf := fset.Position(x.Pos())
				endf := fset.Position(x.End())
				r.Data[0].Totals.Functions.Count++
				if isFunctionCovered(startf, endf, p.Blocks) {
					r.Data[0].Totals.Functions.Covered++
				} else {
					r.Data[0].Totals.Functions.Uncovered++
				}
			}
			return true
		})

		for _, b := range p.Blocks {
			r.Data[0].Totals.Regions.Count++
			if b.Count > 0 {
				r.Data[0].Totals.Regions.Covered++
			} else {
				r.Data[0].Totals.Regions.Uncovered++
			}

			r.Data[0].Totals.Lines.Count += b.NumStmt
			if b.Count > 0 {
				r.Data[0].Totals.Lines.Covered += b.NumStmt
			} else {
				r.Data[0].Totals.Lines.Uncovered += b.NumStmt
			}
		}
	}
	r.Data[0].Totals.Regions.Percent = float64(100*r.Data[0].Totals.Regions.Covered) / float64(r.Data[0].Totals.Regions.Count)
	r.Data[0].Totals.Lines.Percent = float64(100*r.Data[0].Totals.Lines.Covered) / float64(r.Data[0].Totals.Lines.Count)
	r.Data[0].Totals.Functions.Percent = float64(100*r.Data[0].Totals.Functions.Covered) / float64(r.Data[0].Totals.Functions.Count)
	o, _ := json.Marshal(r)
	fmt.Printf(string(o))
}