shad-go/tools/testtool/commands/coverage.go

128 lines
2.6 KiB
Go
Raw Permalink Normal View History

2020-03-18 22:29:11 +00:00
package commands
import (
"fmt"
"go/parser"
"go/token"
"strconv"
"strings"
"golang.org/x/tools/cover"
)
// coverageCommentPrefix is a prefix of coverage comment.
//
// Coverage comment has the following form:
//
// // min coverage: 80.5%
const coverageCommentPrefix = "min coverage: "
type CoverageRequirements struct {
Enabled bool
Percent float64
Packages []string
2020-03-18 22:29:11 +00:00
}
// getCoverageRequirements searches for comment in test files
// that specifies test coverage requirements.
//
// Stops on first matching comment.
func getCoverageRequirements(rootPackage string) *CoverageRequirements {
files := listTestFiles(rootPackage)
for _, f := range files {
if r, _ := searchCoverageComment(f); r.Enabled {
2020-03-18 22:29:11 +00:00
return r
}
}
return &CoverageRequirements{}
2020-03-18 22:29:11 +00:00
}
// searchCoverageComment searches for the first occurrence of the comment of the form
//
// // min coverage: 80.5%
//
// Stops on the first matching comment.
func searchCoverageComment(fname string) (*CoverageRequirements, error) {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, fname, nil, parser.ParseComments)
if err != nil {
return nil, err
}
for _, c := range f.Comments {
t := c.Text()
if !strings.HasPrefix(t, coverageCommentPrefix) || !strings.HasSuffix(t, "%\n") {
continue
}
t = strings.TrimPrefix(t, coverageCommentPrefix)
t = strings.TrimSuffix(t, "%\n")
parts := strings.Split(t, " ")
if len(parts) != 2 {
continue
}
percent, err := strconv.ParseFloat(parts[1], 64)
2020-03-18 22:29:11 +00:00
if err != nil {
continue
}
if percent < 0 || percent > 100.0 {
continue
}
return &CoverageRequirements{
Enabled: true,
Percent: percent,
Packages: strings.Split(parts[0], ","),
}, nil
2020-03-18 22:29:11 +00:00
}
return &CoverageRequirements{}, nil
}
// calCoverage calculates coverage percent for given coverage profile.
func calCoverage(fileNames []string) (float64, error) {
type block struct {
fileName string
startLine, startCol int
endLine, endCol int
numStmt int
2020-03-18 22:29:11 +00:00
}
counts := map[block]int{}
2020-03-18 22:29:11 +00:00
for _, f := range fileNames {
profiles, err := cover.ParseProfiles(f)
if err != nil {
return 0.0, fmt.Errorf("cannot parse coverage profile file %s: %w", f, err)
}
for _, p := range profiles {
for _, b := range p.Blocks {
counts[block{
p.FileName,
b.StartLine, b.StartCol,
b.EndLine, b.EndCol,
b.NumStmt,
}] += b.Count
2020-03-18 22:29:11 +00:00
}
}
}
var total, covered int
for b, count := range counts {
total += b.numStmt
if count > 0 {
covered += b.numStmt
}
}
2020-03-18 22:29:11 +00:00
if total == 0 {
return 0.0, nil
}
return float64(covered) / float64(total) * 100, nil
}