Skip to content

QUA-948: Support .NET DotCover coverage reporting #508

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

Merged
merged 6 commits into from
Mar 3, 2023
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
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ test-cobertura:
test-excoveralls:
docker build -f integration-tests/excoveralls/Dockerfile .

test-dotcover:
docker build -f integration-tests/dotcover/Dockerfile .

publish-head:
$(call upload_artifacts,head)

Expand Down
4 changes: 3 additions & 1 deletion cmd/format-coverage.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/codeclimate/test-reporter/formatters/clover"
"github.com/codeclimate/test-reporter/formatters/cobertura"
"github.com/codeclimate/test-reporter/formatters/coveragepy"
"github.com/codeclimate/test-reporter/formatters/dotcover"
"github.com/codeclimate/test-reporter/formatters/excoveralls"
"github.com/codeclimate/test-reporter/formatters/gcov"
"github.com/codeclimate/test-reporter/formatters/gocov"
Expand All @@ -37,7 +38,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", "lcov-json", "simplecov", "xccov"}
var formatterList = []string{"clover", "cobertura", "coverage.py", "excoveralls", "gcov", "gocov", "jacoco", "lcov", "lcov-json", "simplecov", "xccov", "dotcover"}

// a map of the formatters to use
var formatterMap = map[string]formatters.Formatter{
Expand All @@ -52,6 +53,7 @@ var formatterMap = map[string]formatters.Formatter{
"lcov-json": &lcovjson.Formatter{},
"simplecov": &simplecov.Formatter{},
"xccov": &xccov.Formatter{},
"dotcover": &dotcover.Formatter{},
}

// formatCoverageCmd represents the format command
Expand Down
90 changes: 90 additions & 0 deletions formatters/dotcover/dotcover.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package dotcover

import (
"encoding/xml"
"os"
"strings"

"github.com/Sirupsen/logrus"
"github.com/codeclimate/test-reporter/env"
"github.com/codeclimate/test-reporter/formatters"
"github.com/pkg/errors"
)

var searchPaths = []string{"dotcover.xml"}

// Formatter is the exported struct to be used on format-coverage.go
type Formatter struct {
Path string
}

// Search looks for the dotcover test report file in default paths or provided ones.
func (f *Formatter) Search(paths ...string) (string, error) {
paths = append(paths, searchPaths...)
for _, p := range paths {
logrus.Debugf("checking search path %s for dotcover 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 dotcover. search paths were: %s", strings.Join(paths, ", ")))
}

// Format transforms the provided test report into a CC readable report format.
func (f Formatter) Format() (formatters.Report, error) {
rep, err := formatters.NewReport()
if err != nil {
return rep, err
}

c, err := f.readDotCoverXML()
if err != nil {
return rep, err
}

gitHead, _ := env.GetHead()

for _, file := range c.Files {
sf, err := formatters.NewSourceFile(file.Path, gitHead)
if err != nil {
err = errors.WithStack(err)
break
}

for _, statement := range c.Statements {
if file.Index == statement.FileIndex {
if statement.Covered {
sf.Coverage = append(sf.Coverage, formatters.NewNullInt(1))
} else {
sf.Coverage = append(sf.Coverage, formatters.NewNullInt(0))
}
}
}

err = rep.AddSourceFile(sf)

if err != nil {
err = errors.WithStack(err)
break
}
}

return rep, err
}

// readDotCoverXML reads the dotCover XML file and returns its contents.
func (f Formatter) readDotCoverXML() (*xmlDotCover, error) {
fx, err := os.Open(f.Path)
if err != nil {
return nil, errors.WithStack(err)
}

c := &xmlDotCover{}
if err = xml.NewDecoder(fx).Decode(c); err != nil {
return nil, errors.WithStack(err)
}

return c, nil
}
42 changes: 42 additions & 0 deletions formatters/dotcover/dotcover_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package dotcover

import (
"testing"

"gopkg.in/src-d/go-git.v4/plumbing/object"

"github.com/codeclimate/test-reporter/env"
"github.com/stretchr/testify/require"
)

func Test_Parse(t *testing.T) {
ogb := env.GitBlob
defer func() {
env.GitBlob = ogb
}()
env.GitBlob = func(s string, c *object.Commit) (string, error) {
return s, nil
}

assert := require.New(t)

formatter := Formatter{
Path: "./example.xml",
}
rep, err := formatter.Format()
assert.NoError(err)

assert.Len(rep.SourceFiles, 3)
assert.InDelta(71, rep.CoveredPercent, 1)
assert.Equal(24, rep.LineCounts.Total)
assert.Equal(17, rep.LineCounts.Covered)

sf_one := rep.SourceFiles[`C:\Users\fulano\Desktop\unit-testing-using-mstest\PrimeService\PrimeService.cs`]
assert.InDelta(83, sf_one.CoveredPercent, 1)

sf_two := rep.SourceFiles[`C:\Users\fulano\Desktop\unit-testing-using-mstest\PrimeService\SecondService.cs`]
assert.Equal(0.0, sf_two.CoveredPercent)

sf_three := rep.SourceFiles[`C:\Users\fulano\Desktop\unit-testing-using-mstest\PrimeService.Tests\PrimeService_IsPrimeShould.cs`]
assert.Equal(100.0, sf_three.CoveredPercent)
}
56 changes: 56 additions & 0 deletions formatters/dotcover/example.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<Root CoveredStatements="17" TotalStatements="24" CoveragePercent="71" ReportType="DetailedXml" DotCoverVersion="2022.3.2">
<FileIndices>
<File Index="1" Name="C:\Users\fulano\Desktop\unit-testing-using-mstest\PrimeService.Tests\PrimeService_IsPrimeShould.cs" ChecksumAlgorithm="SHA256" Checksum="23612935A1229C4721145EACB2122A220B0F761AD0DB20CC980DBDB0D5003C2A" />
<File Index="2" Name="C:\Users\fulano\Desktop\unit-testing-using-mstest\PrimeService\PrimeService.cs" ChecksumAlgorithm="SHA256" Checksum="CA22D548019CFF7919E45E7289DC438D453674E827BBFCE249587AD9DDFAD47D" />
<File Index="3" Name="C:\Users\fulano\Desktop\unit-testing-using-mstest\PrimeService\SecondService.cs" ChecksumAlgorithm="SHA256" Checksum="073781814111C3F1C12EB8C41863004150417FC1A9D1E08D7F48C95214356D6E" />
</FileIndices>
<Assembly Name="PrimeService" CoveredStatements="5" TotalStatements="12" CoveragePercent="42">
<Namespace Name="Prime.Services" CoveredStatements="5" TotalStatements="12" CoveragePercent="42">
<Type Name="PrimeService" CoveredStatements="5" TotalStatements="6" CoveragePercent="83">
<Method Name="IsPrime(System.Int32):System.Boolean" CoveredStatements="5" TotalStatements="6" CoveragePercent="83">
<Statement FileIndex="2" Line="8" Column="9" EndLine="8" EndColumn="10" Covered="True" />
<Statement FileIndex="2" Line="9" Column="13" EndLine="9" EndColumn="31" Covered="True" />
<Statement FileIndex="2" Line="10" Column="13" EndLine="10" EndColumn="14" Covered="True" />
<Statement FileIndex="2" Line="11" Column="17" EndLine="11" EndColumn="30" Covered="True" />
<Statement FileIndex="2" Line="13" Column="13" EndLine="13" EndColumn="77" Covered="False" />
<Statement FileIndex="2" Line="14" Column="9" EndLine="14" EndColumn="10" Covered="True" />
</Method>
</Type>
<Type Name="SecondService" CoveredStatements="0" TotalStatements="6" CoveragePercent="0">
<Method Name="IsPrime(System.Int32):System.Boolean" CoveredStatements="0" TotalStatements="6" CoveragePercent="0">
<Statement FileIndex="3" Line="8" Column="9" EndLine="8" EndColumn="10" Covered="False" />
<Statement FileIndex="3" Line="9" Column="13" EndLine="9" EndColumn="31" Covered="False" />
<Statement FileIndex="3" Line="10" Column="13" EndLine="10" EndColumn="14" Covered="False" />
<Statement FileIndex="3" Line="11" Column="17" EndLine="11" EndColumn="30" Covered="False" />
<Statement FileIndex="3" Line="13" Column="13" EndLine="13" EndColumn="77" Covered="False" />
<Statement FileIndex="3" Line="14" Column="9" EndLine="14" EndColumn="10" Covered="False" />
</Method>
</Type>
</Namespace>
</Assembly>
<Assembly Name="PrimeService.Tests" CoveredStatements="12" TotalStatements="12" CoveragePercent="100">
<Namespace Name="Prime.UnitTests.Services" CoveredStatements="12" TotalStatements="12" CoveragePercent="100">
<Type Name="PrimeService_IsPrimeShould" CoveredStatements="12" TotalStatements="12" CoveragePercent="100">
<Method Name=".ctor():System.Void" CoveredStatements="4" TotalStatements="4" CoveragePercent="100">
<Statement FileIndex="1" Line="11" Column="9" EndLine="11" EndColumn="44" Covered="True" />
<Statement FileIndex="1" Line="12" Column="9" EndLine="12" EndColumn="10" Covered="True" />
<Statement FileIndex="1" Line="13" Column="13" EndLine="13" EndColumn="48" Covered="True" />
<Statement FileIndex="1" Line="14" Column="9" EndLine="14" EndColumn="10" Covered="True" />
</Method>
<Method Name="IsPrime_InputIs1_ReturnFalse():System.Void" CoveredStatements="4" TotalStatements="4" CoveragePercent="100">
<Statement FileIndex="1" Line="18" Column="9" EndLine="18" EndColumn="10" Covered="True" />
<Statement FileIndex="1" Line="19" Column="13" EndLine="19" EndColumn="51" Covered="True" />
<Statement FileIndex="1" Line="21" Column="13" EndLine="21" EndColumn="62" Covered="True" />
<Statement FileIndex="1" Line="22" Column="9" EndLine="22" EndColumn="10" Covered="True" />
</Method>
<Method Name="IsPrime_ValuesLessThan2_ReturnFalse(System.Int32):System.Void" CoveredStatements="4" TotalStatements="4" CoveragePercent="100">
<Statement FileIndex="1" Line="30" Column="9" EndLine="30" EndColumn="10" Covered="True" />
<Statement FileIndex="1" Line="31" Column="13" EndLine="31" EndColumn="55" Covered="True" />
<Statement FileIndex="1" Line="33" Column="13" EndLine="33" EndColumn="68" Covered="True" />
<Statement FileIndex="1" Line="34" Column="9" EndLine="34" EndColumn="10" Covered="True" />
</Method>
</Type>
</Namespace>
</Assembly>
</Root>
15 changes: 15 additions & 0 deletions formatters/dotcover/xml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package dotcover

import "encoding/xml"

type xmlDotCover struct {
XMLName xml.Name `xml:"Root"`
Files []struct {
Path string `xml:"Name,attr"`
Index int `xml:"Index,attr"`
} `xml:"FileIndices>File"`
Statements []struct {
FileIndex int `xml:"FileIndex,attr"`
Covered bool `xml:"Covered,attr"`
} `xml:"Assembly>Namespace>Type>Method>Statement"`
}
40 changes: 40 additions & 0 deletions integration-tests/dotcover/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0

# Install GoLang
RUN curl -O https://dl.google.com/go/go1.15.linux-amd64.tar.gz
RUN tar -xzf go1.15.linux-amd64.tar.gz
RUN mv go /usr/local

ENV PATH $PATH:/usr/local/go/bin
ENV GOBIN="/usr/local/go/bin"
RUN go version

ENV GOPATH /go
RUN mkdir $GOPATH
ENV PATH $PATH:/go/bin

ENV CCTR=$GOPATH/src/github.com/codeclimate/test-reporter
RUN mkdir -p $CCTR
WORKDIR $CCTR
COPY . .
RUN go install -v

ENV PATH $PATH:/root/.dotnet/tools
RUN dotnet tool install JetBrains.dotCover.GlobalTool -g --version "2022.3.2"

# Clone .NET example repo and run test
RUN git clone https://github.com/codeclimate/dot-net-coverage-test.git
WORKDIR dot-net-coverage-test
RUN dotnet build
RUN dotnet dotcover test --dcReportType=DetailedXML --dcOutput="dotcover.xml" --no-build

RUN echo "testing" > ignore.me && \
git config --global user.email "[email protected]" && \
git config --global user.name "Your Name" && \
git add ignore.me && \
git commit -m "testing"

ENV CC_TEST_REPORTER_ID=49a59a1849364524250d544e098b5d987335376cdb739ea7649c9f8bce968e3b
RUN test-reporter format-coverage -d -t dotcover
RUN cat coverage/codeclimate.json
RUN test-reporter upload-coverage -d -s 2
6 changes: 5 additions & 1 deletion man/cc-test-reporter-format-coverage.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Locate, parse, and re-format supported coverage sources.

# OPTIONS

## -t, --input-type *simplecov*|*lcov*|*coverage.py*|*gcov*|*clover*
## -t, --input-type *simplecov*|*lcov*|*coverage.py*|*gcov*|*clover*|*dotcover*

Identifies the input type (format) of the COVERAGE_FILE.

Expand Down Expand Up @@ -67,6 +67,10 @@ As generated by **phpunit --coverage-clover**.

As generated by `go test -coverprofile=c.out`

## ./dotcover.xml *DotCover*

As generated by `dotnet dotcover test --dcReportType=DetailedXML --dcOutput="dotcover.xml"`

# ENVIRONMENT VARIABLES

*GIT_BRANCH*, *GIT_COMMIT_SHA*, and *GIT_COMMITTED_AT* are required. *CI_NAME*,
Expand Down