Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c7bf4c9
Update paths coverage
mrodm Dec 22, 2023
bd5a500
Add manifest.yml into the path
mrodm Dec 22, 2023
25cd56f
Add base folder
mrodm Dec 22, 2023
d7c89be
Get filepath with respect to working copy root
mrodm Dec 22, 2023
64dfd9e
Fix linting
mrodm Jan 2, 2024
bba2f9b
Remove Hits from Methods attribute not present in DTD
mrodm Jan 12, 2024
73c0dfe
Add base folder in package name
mrodm Jan 12, 2024
af49010
Add different lines in manifest.yml for each type of test
mrodm Jan 12, 2024
9cb94d8
Add lines into CoberturaClass
mrodm Jan 12, 2024
26ffdac
Check coverage xml files
mrodm Jan 12, 2024
d584340
Remove hits from CoberturaMethod in tests
mrodm Jan 12, 2024
d6e001e
Replace / chars in paths for package names
mrodm Jan 12, 2024
e9e9826
Avoid setting sources for test coverage pipeline
mrodm Jan 12, 2024
83ac6bf
Add generic output - WIP
mrodm Jan 16, 2024
82dc88f
Some refactors
mrodm Jan 16, 2024
811e2cd
Show tests for input packages
mrodm Jan 16, 2024
46ef78b
Refactor and tests
mrodm Jan 16, 2024
00612aa
Refactor
mrodm Jan 16, 2024
738fc69
Ensure data streams are sorted - more tests added
mrodm Jan 16, 2024
522ffd6
Add comments
mrodm Jan 16, 2024
9d153d3
Manage coverage paths for windows
mrodm Jan 16, 2024
9193fd9
Remove base folder from CoberturaPackage
mrodm Jan 16, 2024
0a6eaee
Use filepath for tests
mrodm Jan 16, 2024
f938f12
Update method to get base folder using filepath
mrodm Jan 16, 2024
41531ac
Use filepath for files in cverage reports
mrodm Jan 17, 2024
c961c39
Add package type as part of the coverage report file name
mrodm Jan 17, 2024
23701cd
Add test type as part of the coverage report file name
mrodm Jan 17, 2024
a51cb2f
Remove leftover from testing
mrodm Jan 17, 2024
f667f8c
Remove debug leftovers
mrodm Jan 17, 2024
d98f7b4
Ensure right paths for sources and filenames (pipeline coverage)
mrodm Jan 17, 2024
9b65654
Rephrase comments
mrodm Jan 17, 2024
aa9e30c
Rename createCoverageReport parameter
mrodm Jan 18, 2024
549ffda
Check coverage format value set in the parameter
mrodm Jan 18, 2024
5579da4
Update test scripts to use coverage format generic
mrodm Jan 18, 2024
499953c
Add missing version field
mrodm Jan 18, 2024
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 .buildkite/pipeline.trigger.integration.tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ for test in ${CHECK_PACKAGES_TESTS[@]}; do
echo " - build/test-results/*.xml"
echo " - build/elastic-stack-dump/check-*/logs/*.log"
echo " - build/elastic-stack-dump/check-*/logs/fleet-server-internal/**/*"
echo " - build/test-coverage/coverage-*.xml" # these files should not be used to compute the final coverage of elastic-package
if [[ $test =~ with-kind$ ]]; then
echo " - build/kubectl-dump.txt"
fi
Expand All @@ -63,6 +64,7 @@ for package in $(find . -maxdepth 1 -mindepth 1 -type d) ; do
echo " provider: \"gcp\""
echo " artifact_paths:"
echo " - build/test-results/*.xml"
echo " - build/test-coverage/coverage-*.xml" # these files should not be used to compute the final coverage of elastic-package
done

popd > /dev/null
Expand All @@ -79,6 +81,7 @@ for package in $(find . -maxdepth 1 -mindepth 1 -type d) ; do
echo " provider: \"gcp\""
echo " artifact_paths:"
echo " - build/test-results/*.xml"
echo " - build/test-coverage/coverage-*.xml" # these files should not be used to compute the final coverage of elastic-package
done

popd > /dev/null
Expand Down
14 changes: 13 additions & 1 deletion cmd/testrunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -71,6 +72,7 @@ func setupTestCommand() *cobraext.Command {
cmd.PersistentFlags().StringP(cobraext.ReportFormatFlagName, "", string(formats.ReportFormatHuman), cobraext.ReportFormatFlagDescription)
cmd.PersistentFlags().StringP(cobraext.ReportOutputFlagName, "", string(outputs.ReportOutputSTDOUT), cobraext.ReportOutputFlagDescription)
cmd.PersistentFlags().BoolP(cobraext.TestCoverageFlagName, "", false, cobraext.TestCoverageFlagDescription)
cmd.PersistentFlags().StringP(cobraext.TestCoverageFormatFlagName, "", "cobertura", fmt.Sprintf(cobraext.TestCoverageFormatFlagDescription, strings.Join(testrunner.CoverageFormatsList(), ",")))
cmd.PersistentFlags().DurationP(cobraext.DeferCleanupFlagName, "", 0, cobraext.DeferCleanupFlagDescription)
cmd.PersistentFlags().String(cobraext.VariantFlagName, "", cobraext.VariantFlagDescription)
cmd.PersistentFlags().StringP(cobraext.ProfileFlagName, "p", "", fmt.Sprintf(cobraext.ProfileFlagDescription, install.ProfileNameEnvVar))
Expand Down Expand Up @@ -127,6 +129,15 @@ func testTypeCommandActionFactory(runner testrunner.TestRunner) cobraext.Command
return cobraext.FlagParsingError(err, cobraext.TestCoverageFlagName)
}

testCoverageFormat, err := cmd.Flags().GetString(cobraext.TestCoverageFormatFlagName)
if err != nil {
return cobraext.FlagParsingError(err, cobraext.TestCoverageFormatFlagName)
}

if !slices.Contains(testrunner.CoverageFormatsList(), testCoverageFormat) {
return cobraext.FlagParsingError(fmt.Errorf("coverage format not available: %s", testCoverageFormat), cobraext.TestCoverageFormatFlagName)
}

packageRootPath, found, err := packages.FindPackageRoot()
if !found {
return errors.New("package root not found")
Expand Down Expand Up @@ -246,6 +257,7 @@ func testTypeCommandActionFactory(runner testrunner.TestRunner) cobraext.Command
DeferCleanup: deferCleanup,
ServiceVariant: variantFlag,
WithCoverage: testCoverage,
CoverageType: testCoverageFormat,
})

results = append(results, r...)
Expand All @@ -266,7 +278,7 @@ func testTypeCommandActionFactory(runner testrunner.TestRunner) cobraext.Command
}

if testCoverage {
err := testrunner.WriteCoverage(packageRootPath, manifest.Name, runner.Type(), results)
err := testrunner.WriteCoverage(packageRootPath, manifest.Name, manifest.Type, runner.Type(), results, testCoverageFormat)
if err != nil {
return fmt.Errorf("error writing test coverage: %w", err)
}
Expand Down
5 changes: 4 additions & 1 deletion internal/cobraext/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,10 @@ const (
StatusExtraInfoFlagDescription = "show additional information (comma-separated values: \"%s\")"

TestCoverageFlagName = "test-coverage"
TestCoverageFlagDescription = "generate Cobertura test coverage reports"
TestCoverageFlagDescription = "enable test coverage reports"

TestCoverageFormatFlagName = "coverage-format"
TestCoverageFormatFlagDescription = "set format for coverage reports: %s"

VariantFlagName = "variant"
VariantFlagDescription = "service variant"
Expand Down
2 changes: 2 additions & 0 deletions internal/packages/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ func loadElasticsearchAssets(pkgRootPath string) ([]Asset, error) {
}
}

// TODO add assets for input packages
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created issue for this: #1623


return assets, nil
}

Expand Down
263 changes: 263 additions & 0 deletions internal/testrunner/coberturacoverage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
// 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 testrunner

import (
"bytes"
"encoding/xml"
"fmt"
"path/filepath"
"sort"
)

func init() {
registerCoverageReporterFormat("cobertura")
}

const coverageDtd = `<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">`

// CoberturaCoverage is the root element for a Cobertura XML report.
type CoberturaCoverage struct {
XMLName xml.Name `xml:"coverage"`
LineRate float32 `xml:"line-rate,attr"`
BranchRate float32 `xml:"branch-rate,attr"`
Version string `xml:"version,attr"`
Timestamp int64 `xml:"timestamp,attr"`
LinesCovered int64 `xml:"lines-covered,attr"`
LinesValid int64 `xml:"lines-valid,attr"`
BranchesCovered int64 `xml:"branches-covered,attr"`
BranchesValid int64 `xml:"branches-valid,attr"`
Complexity float32 `xml:"complexity,attr"`
Sources []*CoberturaSource `xml:"sources>source"`
Packages []*CoberturaPackage `xml:"packages>package"`
}

// CoberturaSource represents a base path to the covered source code.
type CoberturaSource struct {
Path string `xml:",chardata"`
}

// CoberturaPackage represents a package in a Cobertura XML report.
type CoberturaPackage struct {
Name string `xml:"name,attr"`
LineRate float32 `xml:"line-rate,attr"`
BranchRate float32 `xml:"branch-rate,attr"`
Complexity float32 `xml:"complexity,attr"`
Classes []*CoberturaClass `xml:"classes>class"`
}

// CoberturaClass represents a class in a Cobertura XML report.
type CoberturaClass struct {
Name string `xml:"name,attr"`
Filename string `xml:"filename,attr"`
LineRate float32 `xml:"line-rate,attr"`
BranchRate float32 `xml:"branch-rate,attr"`
Complexity float32 `xml:"complexity,attr"`
Methods []*CoberturaMethod `xml:"methods>method"`
Lines []*CoberturaLine `xml:"lines>line"`
}

// CoberturaMethod represents a method in a Cobertura XML report.
type CoberturaMethod struct {
Name string `xml:"name,attr"`
Signature string `xml:"signature,attr"`
LineRate float32 `xml:"line-rate,attr"`
BranchRate float32 `xml:"branch-rate,attr"`
Complexity float32 `xml:"complexity,attr"`
Lines []*CoberturaLine `xml:"lines>line"`
}
Comment on lines +63 to +70
Copy link
Contributor Author

@mrodm mrodm Jan 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed Hits field from this struct since it is not present in the DTD

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know if this was used by the jenkins plugin?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just checked the Jenkins plugin code , looking at the code it does not look like it was used. And their tests do not include that field neither

https://github.com/jenkinsci/cobertura-plugin/blob/ff0973461f60c28dbc0bf5ab4853a5aec4798210/src/test/resources/hudson/plugins/cobertura/coverage-with-data.xml


// CoberturaLine represents a source line in a Cobertura XML report.
type CoberturaLine struct {
Number int `xml:"number,attr"`
Hits int64 `xml:"hits,attr"`
}

func (c *CoberturaCoverage) TimeStamp() int64 {
return c.Timestamp
}

func (c *CoberturaCoverage) 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.WriteString(coverageDtd)
buffer.WriteString("\n")
buffer.Write(out)
return buffer.Bytes(), nil
}

// merge merges two coverage reports for a given class.
func (c *CoberturaClass) merge(b *CoberturaClass) error {
// Check preconditions: classes should be the same.
equal := c.Name == b.Name &&
c.Filename == b.Filename &&
len(c.Lines) == len(b.Lines) &&
len(c.Methods) == len(b.Methods)
for idx := range c.Lines {
equal = equal && c.Lines[idx].Number == b.Lines[idx].Number
}
for idx := range c.Methods {
equal = equal && c.Methods[idx].Name == b.Methods[idx].Name &&
len(c.Methods[idx].Lines) == len(b.Methods[idx].Lines)
}
if !equal {
return fmt.Errorf("merging incompatible classes: %+v != %+v", *c, *b)
}
// Update methods
for idx := range b.Methods {
for l := range b.Methods[idx].Lines {
c.Methods[idx].Lines[l].Hits += b.Methods[idx].Lines[l].Hits
}
}
// Rebuild lines
c.Lines = nil
for _, m := range c.Methods {
c.Lines = append(c.Lines, m.Lines...)
}
return nil
}

// merge merges two coverage reports for a given package.
func (p *CoberturaPackage) merge(b *CoberturaPackage) error {
// Merge classes
for _, class := range b.Classes {
var target *CoberturaClass
for _, existing := range p.Classes {
if existing.Name == class.Name {
target = existing
break
}
}
if target != nil {
if err := target.merge(class); err != nil {
return err
}
} else {
p.Classes = append(p.Classes, class)
}
}
return nil
}

// merge merges two coverage reports.
func (c *CoberturaCoverage) Merge(other CoverageReport) error {
b, ok := other.(*CoberturaCoverage)
if !ok {
return fmt.Errorf("not able to assert report to be merged as CoberturaCoverage")

}
// Merge source paths
for _, path := range b.Sources {
found := false
for _, existing := range c.Sources {
if found = existing.Path == path.Path; found {
break
}
}
if !found {
c.Sources = append(c.Sources, path)
}
}

// Merge packages
for _, pkg := range b.Packages {
var target *CoberturaPackage
for _, existing := range c.Packages {
if existing.Name == pkg.Name {
target = existing
break
}
}
if target != nil {
if err := target.merge(pkg); err != nil {
return err
}
} else {
c.Packages = append(c.Packages, pkg)
}
}

// Recalculate global line coverage count
c.LinesValid = 0
c.LinesCovered = 0
for _, pkg := range c.Packages {
for _, cls := range pkg.Classes {
for _, line := range cls.Lines {
c.LinesValid++
if line.Hits > 0 {
c.LinesCovered++
}
}
}
}
return nil
}

func transformToCoberturaReport(details *testCoverageDetails, baseFolder string, timestamp int64) *CoberturaCoverage {
var classes []*CoberturaClass
lineNumberTestType := lineNumberPerTestType(string(details.testType))

// sort data streams to ensure same ordering in coverage arrays
sortedDataStreams := make([]string, 0, len(details.dataStreams))
for dataStream := range details.dataStreams {
sortedDataStreams = append(sortedDataStreams, dataStream)
}
sort.Strings(sortedDataStreams)

for _, dataStream := range sortedDataStreams {
testCases := details.dataStreams[dataStream]

if dataStream == "" && details.packageType == "integration" {
continue // ignore tests running in the package context (not data stream), mostly referring to installed assets
}

var methods []*CoberturaMethod
var lines []*CoberturaLine

if len(testCases) == 0 {
methods = append(methods, &CoberturaMethod{
Name: "Missing",
Lines: []*CoberturaLine{{Number: lineNumberTestType, Hits: 0}},
})
lines = append(lines, []*CoberturaLine{{Number: lineNumberTestType, Hits: 0}}...)
} else {
methods = append(methods, &CoberturaMethod{
Name: "OK",
Lines: []*CoberturaLine{{Number: lineNumberTestType, Hits: 1}},
})
lines = append(lines, []*CoberturaLine{{Number: lineNumberTestType, Hits: 1}}...)
}

fileName := filepath.Join(baseFolder, details.packageName, "data_stream", dataStream, "manifest.yml")
if dataStream == "" {
// input package
fileName = filepath.Join(baseFolder, details.packageName, "manifest.yml")
}

aClass := &CoberturaClass{
Name: string(details.testType),
Filename: fileName,
Methods: methods,
Lines: lines,
}
classes = append(classes, aClass)
}

return &CoberturaCoverage{
Timestamp: timestamp,
Packages: []*CoberturaPackage{
{
Name: details.packageName,
Classes: classes,
},
},
}
}
Loading