...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package bzltestutil
16
17 import (
18 "bufio"
19 "flag"
20 "fmt"
21 "io"
22 "log"
23 "os"
24 "regexp"
25 "sort"
26 "strconv"
27 "strings"
28 "testing/internal/testdeps"
29 )
30
31
32 var coverageDir = os.Getenv("COVERAGE_DIR")
33
34
35
36
37
38 func ConvertCoverToLcov() error {
39 inPath := flag.Lookup("test.coverprofile").Value.String()
40 in, err := os.Open(inPath)
41 if err != nil {
42
43 log.Printf("Not collecting coverage: %s has not been created: %s", inPath, err)
44 return nil
45 }
46 defer in.Close()
47
48 if coverageDir == "" {
49 log.Printf("Not collecting coverage: COVERAGE_DIR is not set")
50 return nil
51 }
52
53 out, err := os.CreateTemp(coverageDir, "go_coverage.*.dat")
54 if err != nil {
55 return err
56 }
57 defer out.Close()
58
59 return convertCoverToLcov(in, out)
60 }
61
62 var _coverLinePattern = regexp.MustCompile(`^(?P<path>.+):(?P<startLine>\d+)\.(?P<startColumn>\d+),(?P<endLine>\d+)\.(?P<endColumn>\d+) (?P<numStmt>\d+) (?P<count>\d+)$`)
63
64 const (
65 _pathIdx = 1
66 _startLineIdx = 2
67 _endLineIdx = 4
68 _countIdx = 7
69 )
70
71 func convertCoverToLcov(coverReader io.Reader, lcovWriter io.Writer) error {
72 cover := bufio.NewScanner(coverReader)
73 lcov := bufio.NewWriter(lcovWriter)
74 defer lcov.Flush()
75 currentPath := ""
76 var lineCounts map[uint32]uint32
77 for cover.Scan() {
78 l := cover.Text()
79 m := _coverLinePattern.FindStringSubmatch(l)
80 if m == nil {
81 if strings.HasPrefix(l, "mode: ") {
82 continue
83 }
84 return fmt.Errorf("invalid go cover line: %s", l)
85 }
86
87 if m[_pathIdx] != currentPath {
88 if currentPath != "" {
89 if err := emitLcovLines(lcov, currentPath, lineCounts); err != nil {
90 return err
91 }
92 }
93 currentPath = m[_pathIdx]
94 lineCounts = make(map[uint32]uint32)
95 }
96
97 startLine, err := strconv.ParseUint(m[_startLineIdx], 10, 32)
98 if err != nil {
99 return err
100 }
101 endLine, err := strconv.ParseUint(m[_endLineIdx], 10, 32)
102 if err != nil {
103 return err
104 }
105 count, err := strconv.ParseUint(m[_countIdx], 10, 32)
106 if err != nil {
107 return err
108 }
109 for line := uint32(startLine); line <= uint32(endLine); line++ {
110 prevCount, ok := lineCounts[line]
111 if !ok || uint32(count) > prevCount {
112 lineCounts[line] = uint32(count)
113 }
114 }
115 }
116 if currentPath != "" {
117 if err := emitLcovLines(lcov, currentPath, lineCounts); err != nil {
118 return err
119 }
120 }
121 return nil
122 }
123
124 func emitLcovLines(lcov io.StringWriter, path string, lineCounts map[uint32]uint32) error {
125 _, err := lcov.WriteString(fmt.Sprintf("SF:%s\n", path))
126 if err != nil {
127 return err
128 }
129
130
131 sortedLines := make([]uint32, 0, len(lineCounts))
132 for line := range lineCounts {
133 sortedLines = append(sortedLines, line)
134 }
135 sort.Slice(sortedLines, func(i, j int) bool { return sortedLines[i] < sortedLines[j] })
136 numCovered := 0
137 for _, line := range sortedLines {
138 count := lineCounts[line]
139 if count > 0 {
140 numCovered++
141 }
142 _, err := lcov.WriteString(fmt.Sprintf("DA:%d,%d\n", line, count))
143 if err != nil {
144 return err
145 }
146 }
147
148 _, err = lcov.WriteString(fmt.Sprintf("LH:%d\nLF:%d\nend_of_record\n", numCovered, len(sortedLines)))
149 if err != nil {
150 return err
151 }
152 return nil
153 }
154
155
156
157
158
159
160
161 type LcovTestDeps struct {
162 testdeps.TestDeps
163 OriginalPanicOnExit bool
164 }
165
166
167
168
169
170
171
172
173 func (ltd LcovTestDeps) SetPanicOnExit0(panicOnExit bool) {
174 if !panicOnExit {
175 lcovAtExitHook()
176 }
177 ltd.TestDeps.SetPanicOnExit0(ltd.OriginalPanicOnExit)
178 }
179
180 func lcovAtExitHook() {
181 if err := ConvertCoverToLcov(); err != nil {
182 log.Printf("Failed to collect coverage: %s", err)
183 os.Exit(TestWrapperAbnormalExit)
184 }
185 }
186
View as plain text