Skip to content

Commit

Permalink
Add support for llvm.coverage.json.export format (#439)
Browse files Browse the repository at this point in the history
* Add support for llvm.coverage.json.export format.

* Remove default searchPaths.

* Update lcovjson.go
  • Loading branch information
paulofaria authored Dec 15, 2020
1 parent d3d488b commit a1a8532
Show file tree
Hide file tree
Showing 5 changed files with 761 additions and 1 deletion.
4 changes: 3 additions & 1 deletion cmd/format-coverage.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/codeclimate/test-reporter/formatters/gocov"
"github.com/codeclimate/test-reporter/formatters/jacoco"
"github.com/codeclimate/test-reporter/formatters/lcov"
"github.com/codeclimate/test-reporter/formatters/lcovjson"
"github.com/codeclimate/test-reporter/formatters/simplecov"
"github.com/codeclimate/test-reporter/formatters/xccov"
"github.com/gobuffalo/envy"
Expand All @@ -36,7 +37,7 @@ type CoverageFormatter struct {
var formatOptions = CoverageFormatter{}

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

// a map of the formatters to use
var formatterMap = map[string]formatters.Formatter{
Expand All @@ -48,6 +49,7 @@ var formatterMap = map[string]formatters.Formatter{
"gocov": &gocov.Formatter{},
"jacoco": &jacoco.Formatter{},
"lcov": &lcov.Formatter{},
"lcov-json": &lcovjson.Formatter{},
"simplecov": &simplecov.Formatter{},
"xccov": &xccov.Formatter{},
}
Expand Down
158 changes: 158 additions & 0 deletions formatters/lcovjson/json_input.go
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"`
}
101 changes: 101 additions & 0 deletions formatters/lcovjson/lcovjson.go
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
}
Loading

0 comments on commit a1a8532

Please sign in to comment.