Skip to content

Commit

Permalink
Merge coverage per file and line (#10315)
Browse files Browse the repository at this point in the history
Merge coverage per file and line instead of just appending the content
of each coverage result.
  • Loading branch information
jsoriano authored Jul 2, 2024
1 parent 0eb2de5 commit 647b31d
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 30 deletions.
29 changes: 0 additions & 29 deletions .buildkite/scripts/merge_xml.sh

This file was deleted.

8 changes: 7 additions & 1 deletion .buildkite/scripts/run_sonar_scanner.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
#!/bin/bash

source .buildkite/scripts/common.sh

set -euo pipefail

add_bin_path
with_mage

run_sonar_scanner() {
local message=""
echo "--- Download coverage reports and merge them"
Expand All @@ -15,7 +21,7 @@ run_sonar_scanner() {
fi

echo "Merge all coverage reports"
.buildkite/scripts/merge_xml.sh
mage mergeCoverage

echo "--- Execute sonar scanner CLI"
/scan-source-code.sh
Expand Down
143 changes: 143 additions & 0 deletions dev/coverage/coverage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

// File partially copied from elastic-package.

package coverage

import (
"bytes"
"encoding/xml"
"fmt"
"os"
)

// GenericCoverage is the root element for a Cobertura XML report.
type GenericCoverage struct {
XMLName xml.Name `xml:"coverage"`
Version int64 `xml:"version,attr"`
Files []*GenericFile `xml:"file"`
Timestamp int64 `xml:"-"`
TestType string `xml:",comment"`
}

type GenericFile struct {
Path string `xml:"path,attr"`
Lines []*GenericLine `xml:"lineToCover"`
}

type GenericLine struct {
LineNumber int64 `xml:"lineNumber,attr"`
Covered bool `xml:"covered,attr"`
}

func (c *GenericCoverage) Bytes() ([]byte, error) {
out, err := xml.MarshalIndent(&c, "", " ")
if err != nil {
return nil, fmt.Errorf("unable to format test results as Coverage: %w", err)
}

var buffer bytes.Buffer
buffer.WriteString(xml.Header)
buffer.WriteString("\n")
buffer.Write(out)
return buffer.Bytes(), nil
}

func (c *GenericFile) merge(b *GenericFile) error {
// Merge files
for _, coverageLine := range b.Lines {
found := false
foundId := 0
for idx, existingLine := range c.Lines {
if existingLine.LineNumber == coverageLine.LineNumber {
found = true
foundId = idx
break
}
}
if !found {
c.Lines = append(c.Lines, coverageLine)
} else {
c.Lines[foundId].Covered = c.Lines[foundId].Covered || coverageLine.Covered
}
}
return nil
}

// merge merges two coverage reports.
func (c *GenericCoverage) Merge(other *GenericCoverage) error {
// Merge files
for _, coverageFile := range other.Files {
var target *GenericFile
for _, existingFile := range c.Files {
if existingFile.Path == coverageFile.Path {
target = existingFile
break
}
}
if target != nil {
if err := target.merge(coverageFile); err != nil {
return err
}
} else {
c.Files = append(c.Files, coverageFile)
}
}
return nil
}

func ReadGenericCoverage(path string) (*GenericCoverage, error) {
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("open failed: %w", err)
}
defer f.Close()

dec := xml.NewDecoder(f)

var coverage GenericCoverage
err = dec.Decode(&coverage)
if err != nil {
return nil, fmt.Errorf("xml decode failed: %w", err)
}

return &coverage, nil
}

func MergeGenericCoverageFiles(paths []string, output string) error {
f, err := os.Create(output)
if err != nil {
return fmt.Errorf("cannot open file %s to write merged coverage: %w", output, err)
}
defer f.Close()

var coverage *GenericCoverage
for _, path := range paths {
c, err := ReadGenericCoverage(path)
if err != nil {
return fmt.Errorf("failed to read coverage from %s: %w", path, err)
}
if coverage == nil {
coverage = c
continue
}
err = coverage.Merge(c)
if err != nil {
return fmt.Errorf("failed to merge coverage from %s: %w", path, err)
}
}

d, err := coverage.Bytes()
if err != nil {
return fmt.Errorf("failed to encode merged coverage: %w", err)
}

_, err = f.Write(d)
if err != nil {
return fmt.Errorf("cannot write merged coverage to %s: %w", output, err)
}

return nil
}
31 changes: 31 additions & 0 deletions dev/coverage/coverage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package coverage

import (
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestMergeCoverage(t *testing.T) {
coverageFiles, err := filepath.Glob("testdata/test-coverage-*.xml")
require.NoError(t, err)

expectedCoverage, err := ReadGenericCoverage("testdata/expected-test-coverage.xml")
require.NoError(t, err)

output := filepath.Join(t.TempDir(), "coverage-merged.xml")

err = MergeGenericCoverageFiles(coverageFiles, output)
require.NoError(t, err)

mergedCoverage, err := ReadGenericCoverage(output)
require.NoError(t, err)

assert.EqualValues(t, expectedCoverage, mergedCoverage)
}
23 changes: 23 additions & 0 deletions dev/coverage/testdata/expected-test-coverage.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>

<coverage version="1">
<file path="packages/elastic_package_registry/data_stream/metrics/fields/base-fields.yml">
<lineToCover lineNumber="1" covered="false"></lineToCover>
<lineToCover lineNumber="2" covered="false"></lineToCover>
<lineToCover lineNumber="3" covered="false"></lineToCover>
<lineToCover lineNumber="4" covered="false"></lineToCover>
<lineToCover lineNumber="5" covered="true"></lineToCover>
<lineToCover lineNumber="6" covered="false"></lineToCover>
<lineToCover lineNumber="7" covered="false"></lineToCover>
<lineToCover lineNumber="8" covered="true"></lineToCover>
<lineToCover lineNumber="9" covered="false"></lineToCover>
<lineToCover lineNumber="10" covered="false"></lineToCover>
<lineToCover lineNumber="11" covered="false"></lineToCover>
<lineToCover lineNumber="12" covered="false"></lineToCover>
</file>
<file path="packages/elastic_package_registry/data_stream/metrics/fields/ecs.yml">
<lineToCover lineNumber="1" covered="true"></lineToCover>
<lineToCover lineNumber="2" covered="true"></lineToCover>
<lineToCover lineNumber="3" covered="true"></lineToCover>
</file>
</coverage>
23 changes: 23 additions & 0 deletions dev/coverage/testdata/test-coverage-1.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>

<coverage version="1">
<file path="packages/elastic_package_registry/data_stream/metrics/fields/base-fields.yml">
<lineToCover lineNumber="1" covered="false"></lineToCover>
<lineToCover lineNumber="2" covered="false"></lineToCover>
<lineToCover lineNumber="3" covered="false"></lineToCover>
<lineToCover lineNumber="4" covered="false"></lineToCover>
<lineToCover lineNumber="5" covered="true"></lineToCover>
<lineToCover lineNumber="6" covered="false"></lineToCover>
<lineToCover lineNumber="7" covered="false"></lineToCover>
<lineToCover lineNumber="8" covered="false"></lineToCover>
<lineToCover lineNumber="9" covered="false"></lineToCover>
<lineToCover lineNumber="10" covered="false"></lineToCover>
<lineToCover lineNumber="11" covered="false"></lineToCover>
<lineToCover lineNumber="12" covered="false"></lineToCover>
</file>
<file path="packages/elastic_package_registry/data_stream/metrics/fields/ecs.yml">
<lineToCover lineNumber="1" covered="false"></lineToCover>
<lineToCover lineNumber="2" covered="false"></lineToCover>
<lineToCover lineNumber="3" covered="false"></lineToCover>
</file>
</coverage>
9 changes: 9 additions & 0 deletions dev/coverage/testdata/test-coverage-2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>

<coverage version="1">
<file path="packages/elastic_package_registry/data_stream/metrics/fields/ecs.yml">
<lineToCover lineNumber="1" covered="false"></lineToCover>
<lineToCover lineNumber="2" covered="false"></lineToCover>
<lineToCover lineNumber="3" covered="false"></lineToCover>
</file>
</coverage>
23 changes: 23 additions & 0 deletions dev/coverage/testdata/test-coverage-3.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>

<coverage version="1">
<file path="packages/elastic_package_registry/data_stream/metrics/fields/base-fields.yml">
<lineToCover lineNumber="1" covered="false"></lineToCover>
<lineToCover lineNumber="2" covered="false"></lineToCover>
<lineToCover lineNumber="3" covered="false"></lineToCover>
<lineToCover lineNumber="4" covered="false"></lineToCover>
<lineToCover lineNumber="5" covered="false"></lineToCover>
<lineToCover lineNumber="6" covered="false"></lineToCover>
<lineToCover lineNumber="7" covered="false"></lineToCover>
<lineToCover lineNumber="8" covered="true"></lineToCover>
<lineToCover lineNumber="9" covered="false"></lineToCover>
<lineToCover lineNumber="10" covered="false"></lineToCover>
<lineToCover lineNumber="11" covered="false"></lineToCover>
<lineToCover lineNumber="12" covered="false"></lineToCover>
</file>
<file path="packages/elastic_package_registry/data_stream/metrics/fields/ecs.yml">
<lineToCover lineNumber="1" covered="true"></lineToCover>
<lineToCover lineNumber="2" covered="true"></lineToCover>
<lineToCover lineNumber="3" covered="true"></lineToCover>
</file>
</coverage>
10 changes: 10 additions & 0 deletions magefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package main

import (
"fmt"
"io"
"os"
"path/filepath"
Expand All @@ -16,6 +17,7 @@ import (
"github.com/pkg/errors"

"github.com/elastic/integrations/dev/codeowners"
"github.com/elastic/integrations/dev/coverage"
)

var (
Expand Down Expand Up @@ -51,6 +53,14 @@ func ImportBeats() error {
return sh.Run("go", args...)
}

func MergeCoverage() error {
coverageFiles, err := filepath.Glob("build/test-coverage/coverage-*.xml")
if err != nil {
return fmt.Errorf("glob failed: %w", err)
}
return coverage.MergeGenericCoverageFiles(coverageFiles, "build/test-coverage/coverage_merged.xml")
}

func build() error {
mg.Deps(buildImportBeats)
return nil
Expand Down

0 comments on commit 647b31d

Please sign in to comment.