From 9dead59364b2e4fd85aae3f0949e18567bf678ab Mon Sep 17 00:00:00 2001 From: viktor-kurchenko Date: Mon, 22 Jul 2024 21:40:42 +0200 Subject: [PATCH] connectivity: single JUnit report 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 --- cli/connectivity.go | 2 - connectivity/check/context.go | 83 -------------------------- connectivity/check/junit.go | 106 ++++++++++++++++++++++++++++++++++ connectivity/suite.go | 5 ++ 4 files changed, 111 insertions(+), 85 deletions(-) create mode 100644 connectivity/check/junit.go diff --git a/cli/connectivity.go b/cli/connectivity.go index 80abe85138..4913b066a8 100644 --- a/cli/connectivity.go +++ b/cli/connectivity.go @@ -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 { @@ -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 diff --git a/connectivity/check/context.go b/connectivity/check/context.go index 2d17cc225b..26d9d92337 100644 --- a/connectivity/check/context.go +++ b/connectivity/check/context.go @@ -5,11 +5,9 @@ package check import ( "context" - "errors" "fmt" "net" "net/netip" - "os" "strconv" "strings" "sync" @@ -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" @@ -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 @@ -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() diff --git a/connectivity/check/junit.go b/connectivity/check/junit.go new file mode 100644 index 0000000000..4cd06612e6 --- /dev/null +++ b/connectivity/check/junit.go @@ -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() +} diff --git a/connectivity/suite.go b/connectivity/suite.go index 4cecc31e37..806a91bee2 100644 --- a/connectivity/suite.go +++ b/connectivity/suite.go @@ -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 @@ -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 }