Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for llvm.coverage.json.export format #439

Merged
merged 3 commits into from
Dec 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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