Skip to content

Commit bbd3044

Browse files
authored
fix duplicate DA record processing (#3)
1 parent 4f26027 commit bbd3044

File tree

5 files changed

+152
-15
lines changed

5 files changed

+152
-15
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# changelog for gcov2lcov
2+
3+
## 1.0.1 [2020-04-25]
4+
5+
* avoid duplicate DA records for same lines (see
6+
https://github.com/jandelgado/gcov2lcov-action/issues/2)
7+
8+
## 1.0.0 [2019-10-07]
9+
10+
* initial release

README.md

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,67 @@ gcov2lcov -inputfile=coverage.out -outfile=coverage.lcov
3434

3535
## Build and Test
3636

37-
* `make test` to run unit tests
37+
* `make test` to run tests
3838
* `make build` to build binary in `bin/` directory
39-
* `make inttest` to run unit integration test
39+
40+
## Tracefile format reference
41+
42+
The following desription is taken from the [geninfo
43+
manpage](http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php) of the [lcov
44+
homepage](http://ltp.sourceforge.net/coverage/lcov/):
45+
46+
```
47+
A tracefile is made up of several human-readable lines of text, divided into sections. If available, a tracefile begins with the testname which is stored in the following format:
48+
49+
TN:<test name>
50+
51+
For each source file referenced in the .da file, there is a section containing filename and coverage data:
52+
53+
SF:<absolute path to the source file>
54+
55+
Following is a list of line numbers for each function name found in the source file:
56+
57+
FN:<line number of function start>,<function name>
58+
59+
Next, there is a list of execution counts for each instrumented function:
60+
61+
FNDA:<execution count>,<function name>
62+
63+
This list is followed by two lines containing the number of functions found and hit:
64+
65+
FNF:<number of functions found> FNH:<number of function hit>
66+
67+
Branch coverage information is stored which one line per branch:
68+
69+
BRDA:<line number>,<block number>,<branch number>,<taken>
70+
71+
Block number and branch number are gcc internal IDs for the branch. Taken is either '-' if the basic block containing the branch was never executed or a number indicating how often that branch was taken.
72+
73+
Branch coverage summaries are stored in two lines:
74+
75+
BRF:<number of branches found> BRH:<number of branches hit>
76+
77+
Then there is a list of execution counts for each instrumented line (i.e. a line which resulted in executable code):
78+
79+
DA:<line number>,<execution count>[,<checksum>]
80+
81+
Note that there may be an optional checksum present for each instrumented line. The current geninfo implementation uses an MD5 hash as checksumming algorithm.
82+
83+
At the end of a section, there is a summary about how many lines were found and how many were actually instrumented:
84+
85+
LH:<number of lines with a non-zero execution count> LF:<number of instrumented lines>
86+
87+
Each sections ends with:
88+
89+
end_of_record
90+
91+
In addition to the main source code file there are sections for all #included files which also contain executable code.
92+
93+
Note that the absolute path of a source file is generated by interpreting the contents of the respective .bb file (see gcov (1) for more information on this file type). Relative filenames are prefixed with the directory in which the .bb file is found.
94+
95+
Note also that symbolic links to the .bb file will be resolved so that the actual file path is used instead of the path to a link. This approach is necessary for the mechanism to work with the /proc/gcov files.
96+
97+
```
4098

4199
## Author
42100

main.go

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"log"
1919
"os"
2020
"path/filepath"
21+
"sort"
2122
"strconv"
2223
"strings"
2324
)
@@ -80,6 +81,16 @@ func getCoverallsSourceFileName(name string) string {
8081
return name
8182
}
8283

84+
func keysOfMap(m map[int]int) []int {
85+
keys := make([]int, len(m))
86+
i := 0
87+
for k := range m {
88+
keys[i] = k
89+
i++
90+
}
91+
return keys
92+
}
93+
8394
func writeLcovRecord(filePath string, blocks []*block, w io.StringWriter) error {
8495

8596
writer := func(err error, s string) error {
@@ -105,18 +116,28 @@ func writeLcovRecord(filePath string, blocks []*block, w io.StringWriter) error
105116
total := 0
106117
covered := 0
107118

119+
// maps line number to sum of covered
120+
coverMap := map[int]int{}
121+
108122
// Loop over each block and extract the lcov data needed.
109123
for _, b := range blocks {
110124
// For each line in a block we add an lcov entry and count the lines.
111125
for i := b.startLine; i <= b.endLine; i++ {
112-
total++
113-
if b.covered > 0 {
114-
covered++
126+
if b.covered < 1 {
127+
continue
115128
}
116-
err = writer(err, "DA:"+strconv.Itoa(i)+","+strconv.Itoa(b.covered)+"\n")
129+
coverMap[i] += b.covered
117130
}
118131
}
119132

133+
lines := keysOfMap(coverMap)
134+
sort.Ints(lines)
135+
for _, line := range lines {
136+
err = writer(err, "DA:"+strconv.Itoa(line)+","+strconv.Itoa(coverMap[line])+"\n")
137+
total++
138+
covered += coverMap[line]
139+
}
140+
120141
err = writer(err, "LF:"+strconv.Itoa(total)+"\n")
121142
err = writer(err, "LH:"+strconv.Itoa(covered)+"\n")
122143

@@ -178,7 +199,7 @@ func parseCoverageLine(line string) (string, *block, error) {
178199
return path[0], b, err
179200
}
180201

181-
func parseCoverage(coverage io.Reader) map[string][]*block {
202+
func parseCoverage(coverage io.Reader) (map[string][]*block, error) {
182203
scanner := bufio.NewScanner(coverage)
183204
blocks := map[string][]*block{}
184205
for scanner.Scan() {
@@ -189,7 +210,7 @@ func parseCoverage(coverage io.Reader) map[string][]*block {
189210
if f, b, err := parseCoverageLine(line); err == nil {
190211
f, err := findFile(f)
191212
if err != nil {
192-
log.Printf("%v", err)
213+
log.Printf("warn: %v", err)
193214
continue
194215
}
195216
f = getCoverallsSourceFileName(f)
@@ -199,14 +220,22 @@ func parseCoverage(coverage io.Reader) map[string][]*block {
199220
}
200221
blocks[f] = append(blocks[f], b)
201222
} else {
202-
log.Printf("%v", err)
223+
log.Printf("warn: %v", err)
203224
}
204225

205226
}
206227
if err := scanner.Err(); err != nil {
207-
log.Fatal(scanner.Err())
228+
return nil, err
229+
}
230+
return blocks, nil
231+
}
232+
233+
func convertCoverage(in io.Reader, out io.Writer) error {
234+
blocks, err := parseCoverage(in)
235+
if err != nil {
236+
return err
208237
}
209-
return blocks
238+
return writeLcov(blocks, out)
210239
}
211240

212241
func main() {
@@ -236,8 +265,8 @@ func main() {
236265
defer outfile.Close()
237266
}
238267

239-
err = writeLcov(parseCoverage(infile), outfile)
268+
err = convertCoverage(infile, outfile)
240269
if err != nil {
241-
log.Fatalf("error writing lcov output: %v", err)
270+
log.Fatalf("convert: %v", err)
242271
}
243272
}

main_test.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,22 @@
33
package main
44

55
import (
6+
"bytes"
67
"strings"
78
"testing"
89

910
"github.com/stretchr/testify/assert"
1011
)
1112

13+
func TestKeysOfMapReturnsAllKeysOfMap(t *testing.T) {
14+
m := map[int]int{1: 10, 10: 100}
15+
16+
keys := keysOfMap(m)
17+
assert.Contains(t, keys, 1)
18+
assert.Contains(t, keys, 10)
19+
assert.Equal(t, 2, len(keys))
20+
}
21+
1222
func TestParseCoverageLineFailsOnInvalidLines(t *testing.T) {
1323
_, _, err := parseCoverageLine("main.go")
1424
assert.NotNil(t, err)
@@ -39,14 +49,15 @@ func TestParseCoverageLineOfParsesValidLineCorrectly(t *testing.T) {
3949

4050
func TestParseCoverage(t *testing.T) {
4151

42-
// note: in this integrative test the package path must match the actual
52+
// note: in this integrative test, the package path must match the actual
4353
// repository name of this project.
4454
cov := `mode: set
4555
github.com/jandelgado/gcov2lcov/main.go:6.14,8.3 2 1`
4656

4757
reader := strings.NewReader(cov)
48-
res := parseCoverage(reader)
58+
res, err := parseCoverage(reader)
4959

60+
assert.NoError(t, err)
5061
assert.Equal(t, 1, len(res))
5162
for k, blks := range res {
5263
assert.Equal(t, 1, len(blks))
@@ -60,3 +71,31 @@ github.com/jandelgado/gcov2lcov/main.go:6.14,8.3 2 1`
6071
assert.Equal(t, 1, b.covered)
6172
}
6273
}
74+
75+
func TestConvertCoverage(t *testing.T) {
76+
// note: in this integrative test, the package path must match the actual
77+
// repository name of this project. Format:
78+
// name.go:line.column,line.column numberOfStatements count
79+
cov := `mode: set
80+
github.com/jandelgado/gcov2lcov/main.go:6.14,8.3 2 1
81+
github.com/jandelgado/gcov2lcov/main.go:7.14,9.3 2 0
82+
github.com/jandelgado/gcov2lcov/main.go:10.1,11.10 2 2`
83+
84+
in := strings.NewReader(cov)
85+
out := bytes.NewBufferString("")
86+
err := convertCoverage(in, out)
87+
88+
expected := `TN:
89+
SF:main.go
90+
DA:6,1
91+
DA:7,1
92+
DA:8,1
93+
DA:10,2
94+
DA:11,2
95+
LF:5
96+
LH:7
97+
end_of_record
98+
`
99+
assert.NoError(t, err)
100+
assert.Equal(t, expected, out.String())
101+
}

testdata/coverage.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
mode: set
22
github.com/jandelgado/gcov2lcov/main.go:45.44,49.38 4 1
3+
github.com/jandelgado/gcov2lcov/main.go:46.44,50.38 4 0
34
github.com/jandelgado/gcov2lcov/main.go:58.2,58.32 1 1

0 commit comments

Comments
 (0)