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 {
|
2020-03-22 00:26:50 +00:00
|
|
|
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 {
|
2020-03-18 22:33:16 +00:00
|
|
|
if r, _ := searchCoverageComment(f); r.Enabled {
|
2020-03-18 22:29:11 +00:00
|
|
|
return r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-18 22:33:16 +00:00
|
|
|
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")
|
2020-03-22 00:26:50 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2020-03-22 00:26:50 +00:00
|
|
|
|
|
|
|
return &CoverageRequirements{
|
|
|
|
Enabled: true,
|
|
|
|
Percent: percent,
|
|
|
|
Packages: strings.Split(parts[0], ","),
|
|
|
|
}, nil
|
2020-03-18 22:29:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &CoverageRequirements{}, nil
|
|
|
|
}
|
|
|
|
|
2020-03-23 20:49:51 +00:00
|
|
|
// calCoverage calculates coverage percent of code blocks recorded in targetProfile
|
|
|
|
// by given coverage profiles.
|
|
|
|
func calCoverage(targetProfile string, fileNames []string) (float64, error) {
|
|
|
|
type key struct {
|
2020-03-21 23:51:13 +00:00
|
|
|
fileName string
|
|
|
|
startLine, startCol int
|
|
|
|
endLine, endCol int
|
|
|
|
numStmt int
|
2020-03-18 22:29:11 +00:00
|
|
|
}
|
2020-03-23 20:49:51 +00:00
|
|
|
newKey := func(p *cover.Profile, b cover.ProfileBlock) key {
|
|
|
|
return key{
|
|
|
|
p.FileName,
|
|
|
|
b.StartLine, b.StartCol,
|
|
|
|
b.EndLine, b.EndCol,
|
|
|
|
b.NumStmt,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
executed := map[key]bool{}
|
|
|
|
targetProfiles, err := cover.ParseProfiles(targetProfile)
|
|
|
|
if err != nil {
|
|
|
|
return 0.0, fmt.Errorf("cannot parse target profile %s: %w", targetProfile, err)
|
|
|
|
}
|
|
|
|
for _, p := range targetProfiles {
|
|
|
|
for _, b := range p.Blocks {
|
|
|
|
executed[newKey(p, b)] = false
|
|
|
|
}
|
|
|
|
}
|
2020-03-18 22:29:11 +00:00
|
|
|
|
2020-03-21 23:51:13 +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 {
|
2020-03-23 20:49:51 +00:00
|
|
|
k := newKey(p, b)
|
|
|
|
if _, ok := executed[k]; ok && b.Count > 0 {
|
|
|
|
executed[k] = true
|
|
|
|
}
|
2020-03-18 22:29:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-21 23:51:13 +00:00
|
|
|
var total, covered int
|
2020-03-23 20:49:51 +00:00
|
|
|
for k, e := range executed {
|
|
|
|
total += k.numStmt
|
|
|
|
if e {
|
|
|
|
covered += k.numStmt
|
2020-03-21 23:51:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-18 22:29:11 +00:00
|
|
|
if total == 0 {
|
|
|
|
return 0.0, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return float64(covered) / float64(total) * 100, nil
|
|
|
|
}
|