-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for llvm.coverage.json.export format (#439)
* Add support for llvm.coverage.json.export format. * Remove default searchPaths. * Update lcovjson.go
- Loading branch information
1 parent
d3d488b
commit a1a8532
Showing
5 changed files
with
761 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
package lcovjson | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
) | ||
|
||
type segment struct { | ||
Line int | ||
Column int | ||
Count int | ||
HasCount bool | ||
IsRegionEntry bool | ||
} | ||
|
||
func (segment *segment) UnmarshalJSON(data []byte) error { | ||
var array []interface{} | ||
if err := json.Unmarshal(data, &array); err != nil { | ||
return err | ||
} | ||
|
||
if n, ok := array[0].(float64); ok { | ||
segment.Line = int(n) | ||
} else { | ||
return errors.New("invalid Line") | ||
} | ||
|
||
if n, ok := array[1].(float64); ok { | ||
segment.Column = int(n) | ||
} else { | ||
return errors.New("invalid Column") | ||
} | ||
|
||
if n, ok := array[2].(float64); ok { | ||
segment.Count = int(n) | ||
} else { | ||
return errors.New("invalid Count") | ||
} | ||
|
||
if b, ok := array[3].(bool); ok { | ||
segment.HasCount = b | ||
} else { | ||
return errors.New("invalid HasCount") | ||
} | ||
|
||
if b, ok := array[4].(bool); ok { | ||
segment.IsRegionEntry = b | ||
} else { | ||
return errors.New("invalid IsRegionEntry") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
type coverage struct { | ||
Count int `json:"count"` | ||
Covered int `json:"covered"` | ||
Percent float64 `json:"percent"` | ||
} | ||
|
||
type summary struct { | ||
Functions coverage `json:"functions"` | ||
Instantiations coverage `json:"instantiations"` | ||
Lines coverage `json:"lines"` | ||
Regions coverage `json:"regions"` | ||
} | ||
|
||
type sourceFile struct { | ||
Filename string `json:"filename"` | ||
Segments []segment `json:"segments"` | ||
Summary summary `json:"summary"` | ||
} | ||
|
||
type region struct { | ||
LineStart int | ||
ColumnStart int | ||
LineEnd int | ||
ColumnEnd int | ||
ExecutionCount int | ||
FileID int | ||
ExpandedFileID int | ||
Kind int | ||
} | ||
|
||
func (region *region) UnmarshalJSON(data []byte) error { | ||
var array []interface{} | ||
if err := json.Unmarshal(data, &array); err != nil { | ||
return err | ||
} | ||
|
||
if n, ok := array[0].(float64); ok { | ||
region.LineStart = int(n) | ||
} else { | ||
return errors.New("invalid LineStart") | ||
} | ||
|
||
if n, ok := array[1].(float64); ok { | ||
region.ColumnStart = int(n) | ||
} else { | ||
return errors.New("invalid ColumnStart") | ||
} | ||
|
||
if n, ok := array[2].(float64); ok { | ||
region.LineEnd = int(n) | ||
} else { | ||
return errors.New("invalid LineEnd") | ||
} | ||
|
||
if n, ok := array[3].(float64); ok { | ||
region.ColumnEnd = int(n) | ||
} else { | ||
return errors.New("invalid ColumnEnd") | ||
} | ||
|
||
if n, ok := array[4].(float64); ok { | ||
region.ExecutionCount = int(n) | ||
} else { | ||
return errors.New("invalid ExecutionCount") | ||
} | ||
|
||
if n, ok := array[5].(float64); ok { | ||
region.FileID = int(n) | ||
} else { | ||
return errors.New("invalid FileID") | ||
} | ||
|
||
if n, ok := array[6].(float64); ok { | ||
region.ExpandedFileID = int(n) | ||
} else { | ||
return errors.New("invalid ExpandedFileID") | ||
} | ||
|
||
if n, ok := array[7].(float64); ok { | ||
region.Kind = int(n) | ||
} else { | ||
return errors.New("invalid Kind") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
type function struct { | ||
Count int `json:"count"` | ||
Filenames []string `json:"filenames"` | ||
Name string `json:"name"` | ||
Regions []region `json:"regions"` | ||
} | ||
|
||
type lcovJsonFile struct { | ||
Data []struct { | ||
Files []sourceFile `json:"files"` | ||
Functions []function `json:"functions"` | ||
Totals summary `json:"totals"` | ||
} `json:"data"` | ||
|
||
Type string `json:"type"` | ||
Version string `json:"version"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package lcovjson | ||
|
||
import ( | ||
"encoding/json" | ||
"os" | ||
"strings" | ||
|
||
"github.com/Sirupsen/logrus" | ||
"github.com/codeclimate/test-reporter/env" | ||
"github.com/codeclimate/test-reporter/formatters" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
type Formatter struct { | ||
Path string | ||
} | ||
|
||
func (f *Formatter) Search(paths ...string) (string, error) { | ||
paths = append(paths) | ||
for _, p := range paths { | ||
logrus.Debugf("checking search path %s for lcov-json formatter", p) | ||
if _, err := os.Stat(p); err == nil { | ||
f.Path = p | ||
return p, nil | ||
} | ||
} | ||
|
||
return "", errors.WithStack(errors.Errorf("could not find any files in search paths for lcov-json. search paths were: %s", strings.Join(paths, ", "))) | ||
} | ||
|
||
func (r Formatter) Format() (formatters.Report, error) { | ||
report, err := formatters.NewReport() | ||
if err != nil { | ||
return report, err | ||
} | ||
|
||
inputLcovJsonFile, err := os.Open(r.Path) | ||
if err != nil { | ||
return report, errors.WithStack(errors.Errorf("could not open coverage file %s", r.Path)) | ||
} | ||
|
||
covFile := &lcovJsonFile{} | ||
err = json.NewDecoder(inputLcovJsonFile).Decode(&covFile) | ||
if err != nil { | ||
return report, errors.WithStack(err) | ||
} | ||
|
||
gitHead, _ := env.GetHead() | ||
for _, target := range covFile.Data { | ||
report.CoveredPercent = target.Totals.Lines.Percent | ||
regionsByFilename := make(map[string][]region) | ||
|
||
for _, function := range target.Functions { | ||
for _, filename := range function.Filenames { | ||
regionsByFilename[filename] = append(regionsByFilename[filename], function.Regions...) | ||
} | ||
} | ||
|
||
for filename, regions := range regionsByFilename { | ||
sourceFile, err := formatters.NewSourceFile(filename, gitHead) | ||
if err != nil { | ||
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) | ||
continue | ||
} | ||
|
||
coverage := make(map[int]formatters.NullInt) | ||
lastLine := 1 | ||
|
||
for _, region := range regions { | ||
for line := region.LineStart; line <= region.LineEnd; line++ { | ||
coverage[line] = formatters.NewNullInt(1) | ||
|
||
if region.ExecutionCount == 0 { | ||
coverage[line] = formatters.NewNullInt(0) | ||
} | ||
|
||
if line > lastLine { | ||
lastLine = line | ||
} | ||
} | ||
} | ||
|
||
for line := 0; line <= lastLine; line++ { | ||
executionCount, isPresent := coverage[line] | ||
|
||
if isPresent { | ||
sourceFile.Coverage = append(sourceFile.Coverage, executionCount) | ||
} else { | ||
sourceFile.Coverage = append(sourceFile.Coverage, formatters.NullInt{}) | ||
} | ||
} | ||
|
||
err = report.AddSourceFile(sourceFile) | ||
if err != nil { | ||
return report, errors.WithStack(err) | ||
} | ||
} | ||
} | ||
|
||
return report, nil | ||
} |
Oops, something went wrong.