Skip to content

Commit a1a8532

Browse files
authored
Add support for llvm.coverage.json.export format (#439)
* Add support for llvm.coverage.json.export format. * Remove default searchPaths. * Update lcovjson.go
1 parent d3d488b commit a1a8532

File tree

5 files changed

+761
-1
lines changed

5 files changed

+761
-1
lines changed

cmd/format-coverage.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/codeclimate/test-reporter/formatters/gocov"
1717
"github.com/codeclimate/test-reporter/formatters/jacoco"
1818
"github.com/codeclimate/test-reporter/formatters/lcov"
19+
"github.com/codeclimate/test-reporter/formatters/lcovjson"
1920
"github.com/codeclimate/test-reporter/formatters/simplecov"
2021
"github.com/codeclimate/test-reporter/formatters/xccov"
2122
"github.com/gobuffalo/envy"
@@ -36,7 +37,7 @@ type CoverageFormatter struct {
3637
var formatOptions = CoverageFormatter{}
3738

3839
// a prioritized list of the formatters to use
39-
var formatterList = []string{"clover", "cobertura", "coverage.py", "excoveralls", "gcov", "gocov", "jacoco", "lcov", "simplecov", "xccov"}
40+
var formatterList = []string{"clover", "cobertura", "coverage.py", "excoveralls", "gcov", "gocov", "jacoco", "lcov", "lcov-json", "simplecov", "xccov"}
4041

4142
// a map of the formatters to use
4243
var formatterMap = map[string]formatters.Formatter{
@@ -48,6 +49,7 @@ var formatterMap = map[string]formatters.Formatter{
4849
"gocov": &gocov.Formatter{},
4950
"jacoco": &jacoco.Formatter{},
5051
"lcov": &lcov.Formatter{},
52+
"lcov-json": &lcovjson.Formatter{},
5153
"simplecov": &simplecov.Formatter{},
5254
"xccov": &xccov.Formatter{},
5355
}

formatters/lcovjson/json_input.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package lcovjson
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
)
7+
8+
type segment struct {
9+
Line int
10+
Column int
11+
Count int
12+
HasCount bool
13+
IsRegionEntry bool
14+
}
15+
16+
func (segment *segment) UnmarshalJSON(data []byte) error {
17+
var array []interface{}
18+
if err := json.Unmarshal(data, &array); err != nil {
19+
return err
20+
}
21+
22+
if n, ok := array[0].(float64); ok {
23+
segment.Line = int(n)
24+
} else {
25+
return errors.New("invalid Line")
26+
}
27+
28+
if n, ok := array[1].(float64); ok {
29+
segment.Column = int(n)
30+
} else {
31+
return errors.New("invalid Column")
32+
}
33+
34+
if n, ok := array[2].(float64); ok {
35+
segment.Count = int(n)
36+
} else {
37+
return errors.New("invalid Count")
38+
}
39+
40+
if b, ok := array[3].(bool); ok {
41+
segment.HasCount = b
42+
} else {
43+
return errors.New("invalid HasCount")
44+
}
45+
46+
if b, ok := array[4].(bool); ok {
47+
segment.IsRegionEntry = b
48+
} else {
49+
return errors.New("invalid IsRegionEntry")
50+
}
51+
52+
return nil
53+
}
54+
55+
type coverage struct {
56+
Count int `json:"count"`
57+
Covered int `json:"covered"`
58+
Percent float64 `json:"percent"`
59+
}
60+
61+
type summary struct {
62+
Functions coverage `json:"functions"`
63+
Instantiations coverage `json:"instantiations"`
64+
Lines coverage `json:"lines"`
65+
Regions coverage `json:"regions"`
66+
}
67+
68+
type sourceFile struct {
69+
Filename string `json:"filename"`
70+
Segments []segment `json:"segments"`
71+
Summary summary `json:"summary"`
72+
}
73+
74+
type region struct {
75+
LineStart int
76+
ColumnStart int
77+
LineEnd int
78+
ColumnEnd int
79+
ExecutionCount int
80+
FileID int
81+
ExpandedFileID int
82+
Kind int
83+
}
84+
85+
func (region *region) UnmarshalJSON(data []byte) error {
86+
var array []interface{}
87+
if err := json.Unmarshal(data, &array); err != nil {
88+
return err
89+
}
90+
91+
if n, ok := array[0].(float64); ok {
92+
region.LineStart = int(n)
93+
} else {
94+
return errors.New("invalid LineStart")
95+
}
96+
97+
if n, ok := array[1].(float64); ok {
98+
region.ColumnStart = int(n)
99+
} else {
100+
return errors.New("invalid ColumnStart")
101+
}
102+
103+
if n, ok := array[2].(float64); ok {
104+
region.LineEnd = int(n)
105+
} else {
106+
return errors.New("invalid LineEnd")
107+
}
108+
109+
if n, ok := array[3].(float64); ok {
110+
region.ColumnEnd = int(n)
111+
} else {
112+
return errors.New("invalid ColumnEnd")
113+
}
114+
115+
if n, ok := array[4].(float64); ok {
116+
region.ExecutionCount = int(n)
117+
} else {
118+
return errors.New("invalid ExecutionCount")
119+
}
120+
121+
if n, ok := array[5].(float64); ok {
122+
region.FileID = int(n)
123+
} else {
124+
return errors.New("invalid FileID")
125+
}
126+
127+
if n, ok := array[6].(float64); ok {
128+
region.ExpandedFileID = int(n)
129+
} else {
130+
return errors.New("invalid ExpandedFileID")
131+
}
132+
133+
if n, ok := array[7].(float64); ok {
134+
region.Kind = int(n)
135+
} else {
136+
return errors.New("invalid Kind")
137+
}
138+
139+
return nil
140+
}
141+
142+
type function struct {
143+
Count int `json:"count"`
144+
Filenames []string `json:"filenames"`
145+
Name string `json:"name"`
146+
Regions []region `json:"regions"`
147+
}
148+
149+
type lcovJsonFile struct {
150+
Data []struct {
151+
Files []sourceFile `json:"files"`
152+
Functions []function `json:"functions"`
153+
Totals summary `json:"totals"`
154+
} `json:"data"`
155+
156+
Type string `json:"type"`
157+
Version string `json:"version"`
158+
}

formatters/lcovjson/lcovjson.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package lcovjson
2+
3+
import (
4+
"encoding/json"
5+
"os"
6+
"strings"
7+
8+
"github.com/Sirupsen/logrus"
9+
"github.com/codeclimate/test-reporter/env"
10+
"github.com/codeclimate/test-reporter/formatters"
11+
"github.com/pkg/errors"
12+
)
13+
14+
type Formatter struct {
15+
Path string
16+
}
17+
18+
func (f *Formatter) Search(paths ...string) (string, error) {
19+
paths = append(paths)
20+
for _, p := range paths {
21+
logrus.Debugf("checking search path %s for lcov-json formatter", p)
22+
if _, err := os.Stat(p); err == nil {
23+
f.Path = p
24+
return p, nil
25+
}
26+
}
27+
28+
return "", errors.WithStack(errors.Errorf("could not find any files in search paths for lcov-json. search paths were: %s", strings.Join(paths, ", ")))
29+
}
30+
31+
func (r Formatter) Format() (formatters.Report, error) {
32+
report, err := formatters.NewReport()
33+
if err != nil {
34+
return report, err
35+
}
36+
37+
inputLcovJsonFile, err := os.Open(r.Path)
38+
if err != nil {
39+
return report, errors.WithStack(errors.Errorf("could not open coverage file %s", r.Path))
40+
}
41+
42+
covFile := &lcovJsonFile{}
43+
err = json.NewDecoder(inputLcovJsonFile).Decode(&covFile)
44+
if err != nil {
45+
return report, errors.WithStack(err)
46+
}
47+
48+
gitHead, _ := env.GetHead()
49+
for _, target := range covFile.Data {
50+
report.CoveredPercent = target.Totals.Lines.Percent
51+
regionsByFilename := make(map[string][]region)
52+
53+
for _, function := range target.Functions {
54+
for _, filename := range function.Filenames {
55+
regionsByFilename[filename] = append(regionsByFilename[filename], function.Regions...)
56+
}
57+
}
58+
59+
for filename, regions := range regionsByFilename {
60+
sourceFile, err := formatters.NewSourceFile(filename, gitHead)
61+
if err != nil {
62+
logrus.Warnf("Couldn't find file at path \"%s\" from %s coverage data. Ignore if the path doesn't correspond to an existent file in your repo.", filename, r.Path)
63+
continue
64+
}
65+
66+
coverage := make(map[int]formatters.NullInt)
67+
lastLine := 1
68+
69+
for _, region := range regions {
70+
for line := region.LineStart; line <= region.LineEnd; line++ {
71+
coverage[line] = formatters.NewNullInt(1)
72+
73+
if region.ExecutionCount == 0 {
74+
coverage[line] = formatters.NewNullInt(0)
75+
}
76+
77+
if line > lastLine {
78+
lastLine = line
79+
}
80+
}
81+
}
82+
83+
for line := 0; line <= lastLine; line++ {
84+
executionCount, isPresent := coverage[line]
85+
86+
if isPresent {
87+
sourceFile.Coverage = append(sourceFile.Coverage, executionCount)
88+
} else {
89+
sourceFile.Coverage = append(sourceFile.Coverage, formatters.NullInt{})
90+
}
91+
}
92+
93+
err = report.AddSourceFile(sourceFile)
94+
if err != nil {
95+
return report, errors.WithStack(err)
96+
}
97+
}
98+
}
99+
100+
return report, nil
101+
}

0 commit comments

Comments
 (0)