Skip to content

Commit

Permalink
connectivity: single JUnit report
Browse files Browse the repository at this point in the history
The commit introduces JUnit collector component that collects
connectivity test data and writes a report into a single file,
even if `test-concurrency` parameter is larger than 1.

Signed-off-by: viktor-kurchenko <[email protected]>
  • Loading branch information
viktor-kurchenko committed Jul 22, 2024
1 parent 628b5cd commit 9dead59
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 85 deletions.
2 changes: 0 additions & 2 deletions cli/connectivity.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"github.com/cilium/cilium-cli/connectivity/check"
"github.com/cilium/cilium-cli/defaults"
"github.com/cilium/cilium-cli/sysdump"
"github.com/cilium/cilium-cli/utils/junit"
)

func newCmdConnectivity(hooks api.Hooks) *cobra.Command {
Expand Down Expand Up @@ -241,7 +240,6 @@ func newConnectivityTests(params check.Parameters, logger *check.ConcurrentLogge
}
params.ExternalDeploymentPort += i
params.EchoServerHostPort += i
params.JunitFile = junit.NamespacedFileName(params.TestNamespace, params.JunitFile)
cc, err := check.NewConnectivityTest(k8sClient, params, defaults.CLIVersion, logger)
if err != nil {
return nil, err
Expand Down
83 changes: 0 additions & 83 deletions connectivity/check/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ package check

import (
"context"
"errors"
"fmt"
"net"
"net/netip"
"os"
"strconv"
"strings"
"sync"
Expand All @@ -26,7 +24,6 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/cilium/cilium-cli/connectivity/internal/junit"
"github.com/cilium/cilium-cli/connectivity/perf/common"
"github.com/cilium/cilium-cli/defaults"
"github.com/cilium/cilium-cli/k8s"
Expand Down Expand Up @@ -427,9 +424,6 @@ func (ct *ConnectivityTest) PrintReport(ctx context.Context) error {
if len(ct.tests) == 0 {
return nil
}
if err := ct.writeJunit(); err != nil {
ct.Failf("writing to junit file %s failed: %s", ct.Params().JunitFile, err)
}

if ct.Params().FlushCT {
var wg sync.WaitGroup
Expand Down Expand Up @@ -468,83 +462,6 @@ func (ct *ConnectivityTest) skip(t *Test, index int, reason string) {
t.skipped = true
}

func (ct *ConnectivityTest) writeJunit() error {
if ct.Params().JunitFile == "" {
return nil
}

properties := []junit.Property{
{Name: "Args", Value: strings.Join(os.Args[3:], "|")},
}
for key, val := range ct.Params().JunitProperties {
properties = append(properties, junit.Property{Name: key, Value: val})
}

suite := &junit.TestSuite{
Name: "connectivity test",
Package: "cilium",
Tests: len(ct.tests),
Properties: &junit.Properties{
Properties: properties,
},
}

for i, t := range ct.tests {
test := &junit.TestCase{
Name: t.Name(),
Classname: "connectivity test",
Status: "passed",
Time: t.completionTime.Sub(t.startTime).Seconds(),
}

// Timestamp of the TestSuite is the first test's start time
if i == 0 {
suite.Timestamp = t.startTime.Format("2006-01-02T15:04:05")
}
suite.Time += test.Time

if t.skipped {
test.Status = "skipped"
test.Skipped = &junit.Skipped{Message: t.Name() + " skipped"}
suite.Skipped++
test.Time = 0
} else if t.failed {
test.Status = "failed"
test.Failure = &junit.Failure{Message: t.Name() + " failed", Type: "failure"}
suite.Failures++
msgs := []string{}
for _, a := range t.failedActions() {
msgs = append(msgs, a.String())
}
test.Failure.Value = strings.Join(msgs, "\n")
}

suite.TestCases = append(suite.TestCases, test)
}

suites := junit.TestSuites{
Tests: suite.Tests,
Disabled: suite.Skipped,
Failures: suite.Failures,
Time: suite.Time,
TestSuites: []*junit.TestSuite{suite},
}

f, err := os.Create(ct.Params().JunitFile)
if err != nil {
return err
}

if err := suites.WriteReport(f); err != nil {
if e := f.Close(); e != nil {
return errors.Join(err, e)
}
return err
}

return f.Close()
}

func (ct *ConnectivityTest) report() error {
total := ct.tests
actions := ct.actions()
Expand Down
106 changes: 106 additions & 0 deletions connectivity/check/junit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package check

import (
"errors"
"os"
"strings"

"github.com/cilium/cilium-cli/connectivity/internal/junit"
)

// NewJUnitCollector factory function that returns JUnitCollector.
func NewJUnitCollector(junitProperties map[string]string, junitFile string) *JUnitCollector {
properties := []junit.Property{
{Name: "Args", Value: strings.Join(os.Args[3:], "|")},
}
for key, val := range junitProperties {
properties = append(properties, junit.Property{Name: key, Value: val})
}
return &JUnitCollector{
testSuite: &junit.TestSuite{
Name: "connectivity test",
Package: "cilium",
Properties: &junit.Properties{
Properties: properties,
},
},
junitFile: junitFile,
}
}

type JUnitCollector struct {
testSuite *junit.TestSuite
junitFile string
}

// Collect collects ConnectivityTest instance test results.
// The method is not thread safe.
func (j *JUnitCollector) Collect(ct *ConnectivityTest) {
if j.junitFile != "" && len(ct.tests) == 0 {
return
}

// Timestamp of the TestSuite is the first test's start time
if j.testSuite.Timestamp == "" {
j.testSuite.Timestamp = ct.tests[0].startTime.Format("2006-01-02T15:04:05")
}
for _, t := range ct.tests {
test := &junit.TestCase{
Name: t.Name(),
Classname: "connectivity test",
Status: "passed",
Time: t.completionTime.Sub(t.startTime).Seconds(),
}
j.testSuite.Tests++
j.testSuite.Time += test.Time

if t.skipped {
test.Status = "skipped"
test.Skipped = &junit.Skipped{Message: t.Name() + " skipped"}
j.testSuite.Skipped++
test.Time = 0
} else if t.failed {
test.Status = "failed"
test.Failure = &junit.Failure{Message: t.Name() + " failed", Type: "failure"}
j.testSuite.Failures++
msgs := []string{}
for _, a := range t.failedActions() {
msgs = append(msgs, a.String())
}
test.Failure.Value = strings.Join(msgs, "\n")
}

j.testSuite.TestCases = append(j.testSuite.TestCases, test)
}
}

// Write writes collected JUnit results into a single report file.
func (j *JUnitCollector) Write() error {
if j.testSuite.Tests == 0 {
return nil
}

suites := junit.TestSuites{
Tests: j.testSuite.Tests,
Disabled: j.testSuite.Skipped,
Failures: j.testSuite.Failures,
Time: j.testSuite.Time,
TestSuites: []*junit.TestSuite{j.testSuite},
}
f, err := os.Create(j.junitFile)
if err != nil {
return err
}

if err := suites.WriteReport(f); err != nil {
if e := f.Close(); e != nil {
return errors.Join(err, e)
}
return err
}

return f.Close()
}
5 changes: 5 additions & 0 deletions connectivity/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func Run(ctx context.Context, connTests []*check.ConnectivityTest, extra Hooks)
if err != nil {
return err
}
junitCollector := check.NewJUnitCollector(connTests[0].Params().JunitProperties, connTests[0].Params().JunitFile)
for i := range suiteBuilders {
if e := suiteBuilders[i](connTests, extra.AddConnectivityTests); e != nil {
return e
Expand All @@ -41,12 +42,16 @@ func Run(ctx context.Context, connTests []*check.ConnectivityTest, extra Hooks)
return e
}
for j := range connTests {
junitCollector.Collect(connTests[j])
if e := connTests[j].PrintReport(ctx); e != nil {
err = errors.Join(err, e)
}
connTests[j].Cleanup()
}
}
if err := junitCollector.Write(); err != nil {
connTests[0].Failf("writing to junit file %s failed: %s", connTests[0].Params().JunitFile, err)
}
return err
}

Expand Down

0 comments on commit 9dead59

Please sign in to comment.