Skip to content

Commit 5863718

Browse files
authored
chore(coverage): find file cosmetics (#150)
1 parent 57b1a88 commit 5863718

File tree

3 files changed

+105
-58
lines changed

3 files changed

+105
-58
lines changed

pkg/testcoverage/coverage/cover.go

Lines changed: 100 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import (
1010
"path/filepath"
1111
"strings"
1212

13-
"github.com/vladopajic/go-test-coverage/v2/pkg/testcoverage/path"
14-
1513
"golang.org/x/tools/cover"
14+
15+
"github.com/vladopajic/go-test-coverage/v2/pkg/testcoverage/path"
1616
)
1717

1818
const IgnoreText = "coverage-ignore"
@@ -23,6 +23,103 @@ type Config struct {
2323
ExcludePaths []string
2424
}
2525

26+
func GenerateCoverageStats(cfg Config) ([]Stats, error) {
27+
profiles, err := parseProfiles(cfg.Profiles)
28+
if err != nil {
29+
return nil, fmt.Errorf("parsing profiles: %w", err)
30+
}
31+
32+
files, err := findFiles(profiles, cfg.LocalPrefix)
33+
if err != nil {
34+
return nil, err
35+
}
36+
37+
fileStats := make([]Stats, 0, len(profiles))
38+
excludeRules := compileExcludePathRules(cfg.ExcludePaths)
39+
40+
for _, profile := range profiles {
41+
fi, ok := files[profile.FileName]
42+
if !ok { // coverage-ignore
43+
// should already be handled above, but let's check it again
44+
return nil, fmt.Errorf("could not find file [%s]", profile.FileName)
45+
}
46+
47+
if ok := matches(excludeRules, fi.noPrefixName); ok {
48+
continue // this file is excluded
49+
}
50+
51+
s, err := coverageForFile(profile, fi)
52+
if err != nil {
53+
return nil, err
54+
}
55+
56+
if s.Total == 0 {
57+
// do not include files that doesn't have statements.
58+
// this can happen when everything is excluded with comment annotations, or
59+
// simply file doesn't have any statement.
60+
//
61+
// note: we are explicitly adding `continue` statement, instead of having code like this:
62+
// if s.Total != 0 {
63+
// fileStats = append(fileStats, s)
64+
// }
65+
// because with `continue` add additional statements in coverage profile which will require
66+
// to have it covered with tests. since this is interesting case, to have it covered
67+
// with tests, we have code written in this way
68+
continue
69+
}
70+
71+
fileStats = append(fileStats, s)
72+
}
73+
74+
return fileStats, nil
75+
}
76+
77+
func coverageForFile(profile *cover.Profile, fi fileInfo) (Stats, error) {
78+
source, err := os.ReadFile(fi.path)
79+
if err != nil { // coverage-ignore
80+
return Stats{}, fmt.Errorf("failed reading file source [%s]: %w", fi.path, err)
81+
}
82+
83+
funcs, blocks, err := findFuncsAndBlocks(source)
84+
if err != nil { // coverage-ignore
85+
return Stats{}, err
86+
}
87+
88+
annotations, err := findAnnotations(source)
89+
if err != nil { // coverage-ignore
90+
return Stats{}, err
91+
}
92+
93+
s := sumCoverage(profile, funcs, blocks, annotations)
94+
s.Name = fi.noPrefixName
95+
96+
return s, nil
97+
}
98+
99+
type fileInfo struct {
100+
path string
101+
noPrefixName string
102+
}
103+
104+
func findFiles(profiles []*cover.Profile, prefix string) (map[string]fileInfo, error) {
105+
result := make(map[string]fileInfo)
106+
findFile := findFileCreator()
107+
108+
for _, profile := range profiles {
109+
file, noPrefixName, err := findFile(profile.FileName, prefix)
110+
if err != nil {
111+
return nil, fmt.Errorf("could not find file [%s]: %w", profile.FileName, err)
112+
}
113+
114+
result[profile.FileName] = fileInfo{
115+
path: file,
116+
noPrefixName: noPrefixName,
117+
}
118+
}
119+
120+
return result, nil
121+
}
122+
26123
func findFileCreator() func(file, prefix string) (string, string, error) {
27124
cache := make(map[string]*build.Package)
28125

@@ -57,56 +154,6 @@ func findFileCreator() func(file, prefix string) (string, string, error) {
57154
}
58155
}
59156

60-
func GenerateCoverageStats(cfg Config) ([]Stats, error) {
61-
profiles, err := parseProfiles(cfg.Profiles)
62-
if err != nil {
63-
return nil, fmt.Errorf("parsing profiles: %w", err)
64-
}
65-
66-
findFile := findFileCreator()
67-
fileStats := make([]Stats, 0, len(profiles))
68-
excludeRules := compileExcludePathRules(cfg.ExcludePaths)
69-
70-
for _, profile := range profiles {
71-
file, noPrefixName, err := findFile(profile.FileName, cfg.LocalPrefix)
72-
if err != nil {
73-
return nil, fmt.Errorf("could not find file [%s]: %w", profile.FileName, err)
74-
}
75-
76-
if ok := matches(excludeRules, noPrefixName); ok {
77-
continue // this file is excluded
78-
}
79-
80-
source, err := os.ReadFile(file)
81-
if err != nil { // coverage-ignore
82-
return nil, fmt.Errorf("failed reading file source [%s]: %w", profile.FileName, err)
83-
}
84-
85-
funcs, blocks, err := findFuncsAndBlocks(source)
86-
if err != nil { // coverage-ignore
87-
return nil, err
88-
}
89-
90-
annotations, err := findAnnotations(source)
91-
if err != nil { // coverage-ignore
92-
return nil, err
93-
}
94-
95-
s := coverageForFile(profile, funcs, blocks, annotations)
96-
if s.Total == 0 {
97-
// do not include files that doesn't have statements
98-
// this can happen when everything is excluded with comment annotations, or
99-
// simply file doesn't have any statement
100-
continue
101-
}
102-
103-
s.Name = noPrefixName
104-
fileStats = append(fileStats, s)
105-
}
106-
107-
return fileStats, nil
108-
}
109-
110157
func findAnnotations(source []byte) ([]extent, error) {
111158
fset := token.NewFileSet()
112159

@@ -207,7 +254,7 @@ func hasExtentWithStartLine(ee []extent, startLine int) bool {
207254
return found
208255
}
209256

210-
func coverageForFile(profile *cover.Profile, funcs, blocks, annotations []extent) Stats {
257+
func sumCoverage(profile *cover.Profile, funcs, blocks, annotations []extent) Stats {
211258
s := Stats{}
212259

213260
for _, f := range funcs {

pkg/testcoverage/coverage/cover_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ func Test_findFuncs(t *testing.T) {
189189
}, blocks)
190190
}
191191

192-
func Test_coverageForFile(t *testing.T) {
192+
func Test_sumCoverage(t *testing.T) {
193193
t.Parallel()
194194

195195
funcs := []Extent{
@@ -205,17 +205,17 @@ func Test_coverageForFile(t *testing.T) {
205205
{StartLine: 12, EndLine: 20, NumStmt: 5},
206206
}}
207207

208-
s := CoverageForFile(profile, funcs, nil, nil)
208+
s := SumCoverage(profile, funcs, nil, nil)
209209
assert.Equal(t, Stats{Total: 10, Covered: 0}, s)
210210

211211
// Coverage should be empty when every function is excluded
212-
s = CoverageForFile(profile, funcs, nil, funcs)
212+
s = SumCoverage(profile, funcs, nil, funcs)
213213
assert.Equal(t, Stats{Total: 0, Covered: 0}, s)
214214

215215
// Case when annotations is set on block (it should ignore whole block)
216216
annotations := []Extent{{StartLine: 4, EndLine: 4}}
217217
blocks := []Extent{{StartLine: 4, EndLine: 10}}
218-
s = CoverageForFile(profile, funcs, blocks, annotations)
218+
s = SumCoverage(profile, funcs, blocks, annotations)
219219
assert.Equal(t, Stats{Total: 7, Covered: 0}, s)
220220
}
221221

pkg/testcoverage/coverage/export_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ var (
55
FindAnnotations = findAnnotations
66
FindFuncsAndBlocks = findFuncsAndBlocks
77
ParseProfiles = parseProfiles
8-
CoverageForFile = coverageForFile
8+
SumCoverage = sumCoverage
99
)
1010

1111
type Extent = extent

0 commit comments

Comments
 (0)