@@ -10,9 +10,9 @@ import (
10
10
"path/filepath"
11
11
"strings"
12
12
13
- "github.com/vladopajic/go-test-coverage/v2/pkg/testcoverage/path"
14
-
15
13
"golang.org/x/tools/cover"
14
+
15
+ "github.com/vladopajic/go-test-coverage/v2/pkg/testcoverage/path"
16
16
)
17
17
18
18
const IgnoreText = "coverage-ignore"
@@ -23,6 +23,103 @@ type Config struct {
23
23
ExcludePaths []string
24
24
}
25
25
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
+
26
123
func findFileCreator () func (file , prefix string ) (string , string , error ) {
27
124
cache := make (map [string ]* build.Package )
28
125
@@ -57,56 +154,6 @@ func findFileCreator() func(file, prefix string) (string, string, error) {
57
154
}
58
155
}
59
156
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
-
110
157
func findAnnotations (source []byte ) ([]extent , error ) {
111
158
fset := token .NewFileSet ()
112
159
@@ -207,7 +254,7 @@ func hasExtentWithStartLine(ee []extent, startLine int) bool {
207
254
return found
208
255
}
209
256
210
- func coverageForFile (profile * cover.Profile , funcs , blocks , annotations []extent ) Stats {
257
+ func sumCoverage (profile * cover.Profile , funcs , blocks , annotations []extent ) Stats {
211
258
s := Stats {}
212
259
213
260
for _ , f := range funcs {
0 commit comments