Skip to content

Commit 8acbd4d

Browse files
committed
initial commit
0 parents  commit 8acbd4d

File tree

10 files changed

+447
-0
lines changed

10 files changed

+447
-0
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
on: [push, pull_request]
2+
name: run tests
3+
jobs:
4+
lint:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- name: Install Go
8+
uses: actions/setup-go@v1
9+
with:
10+
go-version: 1.13.x
11+
- name: Checkout code
12+
uses: actions/checkout@v1
13+
- name: Install golangci-lint
14+
run: |
15+
go get github.com/golangci/golangci-lint/cmd/golangci-lint
16+
- name: Run linters
17+
run: |
18+
export PATH=$PATH:$(go env GOPATH)/bin
19+
./pre-commit
20+
21+
test:
22+
runs-on: ubuntu-latest
23+
steps:
24+
- name: Install Go
25+
if: success()
26+
uses: actions/setup-go@v1
27+
with:
28+
go-version: 1.13.x
29+
- name: Checkout code
30+
uses: actions/checkout@v1
31+
- name: Run tests
32+
run: make test
33+
34+
build:
35+
runs-on: ubuntu-latest
36+
needs: [lint, test]
37+
steps:
38+
- name: Install Go
39+
uses: actions/setup-go@v1
40+
with:
41+
go-version: 1.13.x
42+
- name: Checkout code
43+
uses: actions/checkout@v1
44+
- name: build
45+
run: |
46+
export GO111MODULE=on
47+
make build
48+
- name: upload artifacts
49+
uses: actions/upload-artifact@master
50+
with:
51+
name: binaries
52+
path: bin/

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*~
2+
*.exe
3+
bin/
4+
*.out

LICENSE

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
MIT License
2+
3+
Copyright (c) 2019 Richard S Allinson
4+
Copyright (c) 2019 Jan Delgado
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
build:
2+
go build -o bin/gcov2lcov .
3+
4+
test:
5+
go test ./... -coverprofile coverage.out
6+
go tool cover -func coverage.out

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# gcov2lcov
2+
3+
Convert golang test coverage to lcov format (which can be uploaded to
4+
coveralls).
5+
6+
See [gcov2lcov-action](https://github.com/jandelgado/gcov2lcov-action)
7+
for an github action which uses this tool.
8+
9+
## Credits
10+
11+
This tool is based on [covfmt](https://github.com/ricallinson/covfmt) and
12+
uses some parts of [goveralls](https://github.com/mattn/goveralls).
13+
14+
## Usage
15+
16+
```
17+
Usage of ./gcov2lcov:
18+
-infile string
19+
go coverage file to read, default: <stdin>
20+
-outfile string
21+
lcov file to write, default: <stdout>
22+
```
23+
24+
### Example
25+
26+
```
27+
$ go test -coverprofile=coverage.out && \
28+
gcov2lcov -inputfile=coverage.out -outfile=coverage.lcov
29+
```
30+
31+
## Build and Test
32+
33+
Run `make test` or `make build`.
34+
35+
## Author
36+
37+
Jan Delgado
38+
39+
## License
40+
41+
MIT

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/jandelgado/gcov2lcov
2+
3+
go 1.12
4+
5+
require github.com/stretchr/testify v1.4.0

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
2+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6+
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
7+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
8+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
9+
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
10+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

main.go

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
// gcov2lcov - convert golang coverage files to the lcov format.
2+
//
3+
// Copyright (c) 2019 Jan Delgado
4+
// Copyright (c) 2019 Richard S Allinson
5+
//
6+
// Credits:
7+
// This tool is based on covfmt (https://github.com/ricallinson/covfmt) and
8+
// uses some parts of goveralls (https://github.com/mattn/goveralls).
9+
//
10+
package main
11+
12+
import (
13+
"bufio"
14+
"errors"
15+
"flag"
16+
"go/build"
17+
"io"
18+
"log"
19+
"os"
20+
"path/filepath"
21+
"strconv"
22+
"strings"
23+
)
24+
25+
type block struct {
26+
startLine int
27+
startChar int
28+
endLine int
29+
endChar int
30+
statements int
31+
covered int
32+
}
33+
34+
var vscDirs = []string{".git", ".hg", ".bzr", ".svn"}
35+
36+
type cacheEntry struct {
37+
file string
38+
err error
39+
}
40+
41+
var pkgCache = map[string]cacheEntry{}
42+
43+
// given a module+file spec (e.g. github.com/jandelgado/gcov2lcov/main.go),
44+
// strip of the module name and return the file name (e.g. main.go).
45+
func findFile(file string) (string, error) {
46+
dir, file := filepath.Split(file)
47+
var result cacheEntry
48+
var ok bool
49+
if result, ok = pkgCache[file]; !ok {
50+
pkg, err := build.Import(dir, ".", build.FindOnly)
51+
if err == nil {
52+
result = cacheEntry{filepath.Join(pkg.Dir, file), nil}
53+
} else {
54+
result = cacheEntry{"", err}
55+
}
56+
pkgCache[file] = result
57+
}
58+
return result.file, result.err
59+
}
60+
61+
// findRepositoryRoot finds the VCS root dir of a given dir
62+
func findRepositoryRoot(dir string) (string, bool) {
63+
for _, vcsdir := range vscDirs {
64+
if d, err := os.Stat(filepath.Join(dir, vcsdir)); err == nil && d.IsDir() {
65+
return dir, true
66+
}
67+
}
68+
nextdir := filepath.Dir(dir)
69+
if nextdir == dir {
70+
return "", false
71+
}
72+
return findRepositoryRoot(nextdir)
73+
}
74+
75+
func getCoverallsSourceFileName(name string) string {
76+
if dir, ok := findRepositoryRoot(name); ok {
77+
filename := strings.TrimPrefix(name, dir+string(os.PathSeparator))
78+
return filename
79+
}
80+
return name
81+
}
82+
83+
func writeLcovRecord(filePath string, blocks []*block, w io.StringWriter) error {
84+
85+
writer := func(err error, s string) error {
86+
if err != nil {
87+
return err
88+
}
89+
_, err = w.WriteString(s)
90+
return err
91+
}
92+
var err error
93+
err = writer(err, "TN:\nSF:"+filePath+"\n")
94+
95+
// Loop over functions
96+
// FN: line,name
97+
98+
// FNF: total functions
99+
// FNH: covered functions
100+
101+
// Loop over functions
102+
// FNDA: stats,name ?
103+
104+
// Loop over lines
105+
total := 0
106+
covered := 0
107+
108+
// Loop over each block and extract the lcov data needed.
109+
for _, b := range blocks {
110+
// For each line in a block we add an lcov entry and count the lines.
111+
for i := b.startLine; i <= b.endLine; i++ {
112+
total++
113+
if b.covered > 0 {
114+
covered++
115+
}
116+
err = writer(err, "DA:"+strconv.Itoa(i)+","+strconv.Itoa(b.covered)+"\n")
117+
}
118+
}
119+
120+
err = writer(err, "LF:"+strconv.Itoa(total)+"\n")
121+
err = writer(err, "LH:"+strconv.Itoa(covered)+"\n")
122+
123+
// Loop over branches
124+
// BRDA: ?
125+
126+
// BRF: total branches
127+
// BRH: covered branches
128+
129+
return writer(err, "end_of_record\n")
130+
}
131+
132+
func writeLcov(blocks map[string][]*block, f io.Writer) error {
133+
w := bufio.NewWriter(f)
134+
for file, fileBlocks := range blocks {
135+
if err := writeLcovRecord(file, fileBlocks, w); err != nil {
136+
return err
137+
}
138+
}
139+
w.Flush()
140+
return nil
141+
}
142+
143+
// Format being parsed is:
144+
// name.go:line.column,line.column numberOfStatements count
145+
// e.g.
146+
// github.com/jandelgado/golang-ci-template/main.go:6.14,8.2 1 1
147+
func parseCoverageLine(line string) (string, *block, error) {
148+
path := strings.Split(line, ":")
149+
if len(path) != 2 {
150+
return "", nil, errors.New("unexpected format (path sep): " + line)
151+
}
152+
parts := strings.Split(path[1], " ")
153+
if len(parts) != 3 {
154+
return "", nil, errors.New("unexpected format (parts): " + line)
155+
}
156+
sections := strings.Split(parts[0], ",")
157+
if len(sections) != 2 {
158+
return "", nil, errors.New("unexpected format (pos): " + line)
159+
}
160+
start := strings.Split(sections[0], ".")
161+
end := strings.Split(sections[1], ".")
162+
163+
safeAtoi := func(err error, s string) (int, error) {
164+
if err != nil {
165+
return 0, err
166+
}
167+
return strconv.Atoi(s)
168+
}
169+
b := &block{}
170+
var err error
171+
b.startLine, err = safeAtoi(nil, start[0])
172+
b.startChar, err = safeAtoi(err, start[1])
173+
b.endLine, err = safeAtoi(err, end[0])
174+
b.endChar, err = safeAtoi(err, end[1])
175+
b.statements, err = safeAtoi(err, parts[1])
176+
b.covered, err = safeAtoi(err, parts[2])
177+
178+
return path[0], b, err
179+
}
180+
181+
func parseCoverage(coverage io.Reader) map[string][]*block {
182+
scanner := bufio.NewScanner(coverage)
183+
blocks := map[string][]*block{}
184+
for scanner.Scan() {
185+
line := scanner.Text()
186+
if strings.HasPrefix(line, "mode:") {
187+
continue
188+
}
189+
if f, b, err := parseCoverageLine(line); err == nil {
190+
f, err := findFile(f)
191+
if err != nil {
192+
log.Printf("%v", err)
193+
continue
194+
}
195+
f = getCoverallsSourceFileName(f)
196+
// Make sure the filePath is a key in the map.
197+
if _, found := blocks[f]; !found {
198+
blocks[f] = []*block{}
199+
}
200+
blocks[f] = append(blocks[f], b)
201+
} else {
202+
log.Printf("%v", err)
203+
}
204+
205+
}
206+
if err := scanner.Err(); err != nil {
207+
log.Fatal(scanner.Err())
208+
}
209+
return blocks
210+
}
211+
212+
func main() {
213+
infileName := flag.String("infile", "", "go coverage file to read, default: <stdin>")
214+
outfileName := flag.String("outfile", "", "lcov file to write, default: <stdout>")
215+
flag.Parse()
216+
if len(flag.Args()) > 0 {
217+
flag.Usage()
218+
os.Exit(1)
219+
}
220+
221+
infile := os.Stdin
222+
outfile := os.Stdout
223+
var err error
224+
if *infileName != "" {
225+
infile, err = os.Open(*infileName)
226+
if err != nil {
227+
log.Fatalf("error opening input file: %v", err)
228+
}
229+
defer infile.Close()
230+
}
231+
if *outfileName != "" {
232+
outfile, err = os.Create(*outfileName)
233+
if err != nil {
234+
log.Fatalf("error opening output file: %v", err)
235+
}
236+
defer outfile.Close()
237+
}
238+
239+
err = writeLcov(parseCoverage(infile), outfile)
240+
if err != nil {
241+
log.Fatalf("error writing lcov output: %v", err)
242+
}
243+
}

0 commit comments

Comments
 (0)