From 1f2e656974ae1bacfcc19e0f2efb2529554f26ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 19 May 2026 13:39:36 +0200 Subject: [PATCH] Add OTE (openshift-tests-extension) entry point Add cmd/extension/main.go as a new OTE-based test extension binary alongside the existing openshift-tests entry point. This registers all openstack tests with the OTE framework, adds [OTP] annotations to all Describe blocks, and integrates the extension build into the Makefile and Containerfile. The OTE binary discovers 51 tests and does not pull in origin's monitor test framework, avoiding the duplicate test results seen with the old entry point. --- Containerfile | 13 + Makefile | 18 + cmd/extension/main.go | 139 ++ go.mod | 2 + go.sum | 8 +- test/extended/openstack/bz_2022627.go | 2 +- test/extended/openstack/bz_2073398.go | 2 +- .../openstack/cloud-provider-config.go | 2 +- test/extended/openstack/cpms.go | 2 +- test/extended/openstack/dualstack.go | 2 +- test/extended/openstack/egressip.go | 2 +- test/extended/openstack/kuryr.go | 2 +- test/extended/openstack/loadbalancers.go | 2 +- test/extended/openstack/local-bd-etcd.go | 2 +- test/extended/openstack/machine.go | 2 +- test/extended/openstack/machineset.go | 2 +- test/extended/openstack/observability.go | 2 +- test/extended/openstack/ocpbug_1765.go | 2 +- test/extended/openstack/servergroup.go | 2 +- test/extended/openstack/topology.go | 2 +- test/extended/openstack/volumes.go | 2 +- .../generated/zz_generated.annotations.go | 102 +- .../openshift-tests-extension/LICENSE | 201 +++ .../openshift-tests-extension/pkg/cmd/cmd.go | 23 + .../pkg/cmd/cmdimages/cmdimages.go | 36 + .../pkg/cmd/cmdinfo/info.go | 38 + .../pkg/cmd/cmdlist/list.go | 133 ++ .../pkg/cmd/cmdrun/runsuite.go | 161 ++ .../pkg/cmd/cmdrun/runtest.go | 113 ++ .../pkg/cmd/cmdupdate/update.go | 84 + .../pkg/dbtime/time.go | 26 + .../pkg/extension/extension.go | 165 ++ .../extension/extensiontests/environment.go | 92 + .../pkg/extension/extensiontests/result.go | 125 ++ .../extension/extensiontests/result_writer.go | 213 +++ .../pkg/extension/extensiontests/spec.go | 621 +++++++ .../pkg/extension/extensiontests/task.go | 31 + .../pkg/extension/extensiontests/types.go | 124 ++ .../pkg/extension/extensiontests/viewer.html | 1520 +++++++++++++++++ .../pkg/extension/registry.go | 39 + .../pkg/extension/types.go | 94 + .../pkg/flags/component.go | 25 + .../pkg/flags/concurrency.go | 23 + .../pkg/flags/environment.go | 114 ++ .../pkg/flags/names.go | 24 + .../pkg/flags/output.go | 96 ++ .../pkg/flags/suite.go | 21 + .../pkg/ginkgo/logging.go | 25 + .../pkg/ginkgo/parallel.go | 139 ++ .../pkg/ginkgo/util.go | 231 +++ .../pkg/junit/types.go | 104 ++ .../pkg/util/sets/LICENSE | 202 +++ .../pkg/util/sets/README.md | 3 + .../pkg/util/sets/byte.go | 137 ++ .../pkg/util/sets/doc.go | 19 + .../pkg/util/sets/empty.go | 21 + .../pkg/util/sets/int.go | 137 ++ .../pkg/util/sets/int32.go | 137 ++ .../pkg/util/sets/int64.go | 137 ++ .../pkg/util/sets/set.go | 236 +++ .../pkg/util/sets/string.go | 137 ++ .../pkg/version/version.go | 11 + vendor/modules.txt | 19 +- 63 files changed, 6079 insertions(+), 72 deletions(-) create mode 100644 cmd/extension/main.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/LICENSE create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmd.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdimages/cmdimages.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdinfo/info.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdlist/list.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun/runsuite.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun/runtest.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdupdate/update.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/dbtime/time.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extension.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/environment.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/result.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/result_writer.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/spec.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/task.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/types.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/viewer.html create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/registry.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/types.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/component.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/concurrency.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/environment.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/names.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/output.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/suite.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/logging.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/parallel.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/util.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/junit/types.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/LICENSE create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/README.md create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/byte.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/doc.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/empty.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/int.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/int32.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/int64.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/set.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/string.go create mode 100644 vendor/github.com/openshift-eng/openshift-tests-extension/pkg/version/version.go diff --git a/Containerfile b/Containerfile index 07c64b47b..23aa085f7 100644 --- a/Containerfile +++ b/Containerfile @@ -7,11 +7,24 @@ COPY ./ ./ RUN go build -o /bin/openstack-test ./cmd/openshift-tests +# Test extension builder stage (added by ote-migration) +FROM golang:1.21 AS test-extension-builder +RUN mkdir -p /go/src/github.com/openshift/openstack-test +WORKDIR /go/src/github.com/openshift/openstack-test +COPY . . +RUN make tests-ext-build && \ + cd bin && \ + tar -czvf openstack-test-test-extension.tar.gz openstack-test-tests-ext && \ + rm -f openstack-test-tests-ext + # Will be replaced in the CI with registry.ci.openshift.org/ocp/4.y:tools FROM registry.access.redhat.com/ubi8/ubi COPY --from=builder /bin/openstack-test /usr/bin/ +# Copy test extension binary (added by ote-migration) +COPY --from=test-extension-builder /go/src/github.com/openshift/openstack-test/bin/openstack-test-test-extension.tar.gz /usr/bin/ + USER 1000:1000 ENV LC_ALL en_US.UTF-8 diff --git a/Makefile b/Makefile index 0794985a4..2e46dc4fb 100644 --- a/Makefile +++ b/Makefile @@ -15,3 +15,21 @@ verify: run: openstack-tests ./$< run openshift/openstack .PHONY: run + +# OTE test extension binary configuration +TESTS_EXT_BINARY := bin/openstack-test-tests-ext + +.PHONY: tests-ext-build +tests-ext-build: + @echo "Building OTE test extension binary..." + @mkdir -p bin + GOTOOLCHAIN=auto GOSUMDB=sum.golang.org go build -mod=vendor -o $(TESTS_EXT_BINARY) ./cmd/extension + @echo "Extension binary built: $(TESTS_EXT_BINARY)" + +.PHONY: extension +extension: tests-ext-build + +.PHONY: clean-extension +clean-extension: + @echo "Cleaning extension binary..." + @rm -f $(TESTS_EXT_BINARY) diff --git a/cmd/extension/main.go b/cmd/extension/main.go new file mode 100644 index 000000000..63f349b4e --- /dev/null +++ b/cmd/extension/main.go @@ -0,0 +1,139 @@ +package main + +import ( + "flag" + "fmt" + "os" + "regexp" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "k8s.io/component-base/logs" + + "github.com/openshift-eng/openshift-tests-extension/pkg/cmd" + e "github.com/openshift-eng/openshift-tests-extension/pkg/extension" + et "github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests" + g "github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo" + "github.com/openshift/origin/test/extended/util" + framework "k8s.io/kubernetes/test/e2e/framework" + + _ "github.com/openshift/openstack-test/test/extended/openstack" +) + +func main() { + pflag.CommandLine = pflag.NewFlagSet("empty", pflag.ExitOnError) + flag.CommandLine = flag.NewFlagSet("empty", flag.ExitOnError) + util.InitStandardFlags() + framework.AfterReadingAllFlags(&framework.TestContext) + + logs.InitLogs() + defer logs.FlushLogs() + + registry := e.NewRegistry() + ext := e.NewExtension("openshift", "payload", "openstack-test") + + registerSuites(ext) + + allSpecs, err := g.BuildExtensionTestSpecsFromOpenShiftGinkgoSuite() + if err != nil { + panic(fmt.Sprintf("couldn't build extension test specs from ginkgo: %+v", err.Error())) + } + + componentSpecs := allSpecs.Select(func(spec *et.ExtensionTestSpec) bool { + for _, loc := range spec.CodeLocations { + if strings.Contains(loc, "/test/extended/openstack/") && !strings.Contains(loc, "/go/pkg/mod/") && !strings.Contains(loc, "/vendor/") { + return true + } + } + return false + }) + + componentSpecs.AddBeforeAll(func() { + if err := util.InitTest(false); err != nil { + panic(err) + } + util.WithCleanup(func() {}) + }) + + componentSpecs.Walk(func(spec *et.ExtensionTestSpec) { + for label := range spec.Labels { + if strings.HasPrefix(label, "Platform:") { + platformName := strings.TrimPrefix(label, "Platform:") + spec.Include(et.PlatformEquals(platformName)) + } + } + + re := regexp.MustCompile(`\[platform:([a-z]+)\]`) + if match := re.FindStringSubmatch(spec.Name); match != nil { + platform := match[1] + spec.Include(et.PlatformEquals(platform)) + } + + spec.Lifecycle = et.LifecycleInforming + }) + + ext.AddSpecs(componentSpecs) + + registry.Register(ext) + + root := &cobra.Command{ + Long: "Openstack-test Tests", + } + + root.AddCommand(cmd.DefaultExtensionCommands(registry)...) + + if err := func() error { + return root.Execute() + }(); err != nil { + os.Exit(1) + } +} + +func registerSuites(ext *e.Extension) { + suites := []e.Suite{ + { + Name: "openstack-test/conformance/parallel", + Parents: []string{ + "openshift/conformance/parallel", + }, + Description: "Parallel conformance tests (Level0, non-serial, non-disruptive)", + Qualifiers: []string{ + `name.contains("[Level0]") && !(name.contains("[Serial]") || name.contains("[Disruptive]"))`, + }, + }, + { + Name: "openstack-test/conformance/serial", + Parents: []string{ + "openshift/conformance/serial", + }, + Description: "Serial conformance tests (must run sequentially)", + Qualifiers: []string{ + `name.contains("[Level0]") && name.contains("[Serial]") && !name.contains("[Disruptive]")`, + }, + }, + { + Name: "openstack-test/disruptive", + Parents: []string{"openshift/disruptive"}, + Description: "Disruptive tests (may affect cluster state)", + Qualifiers: []string{ + `name.contains("[Disruptive]")`, + }, + }, + { + Name: "openstack-test/non-disruptive", + Description: "All non-disruptive tests (safe for development clusters)", + Qualifiers: []string{ + `!name.contains("[Disruptive]")`, + }, + }, + { + Name: "openstack-test/all", + Description: "All openstack-test tests", + }, + } + + for _, suite := range suites { + ext.AddSuite(suite) + } +} diff --git a/go.mod b/go.mod index b8af42ccb..bea0564fc 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/gophercloud/gophercloud/v2 v2.7.0 github.com/onsi/ginkgo/v2 v2.22.2 github.com/onsi/gomega v1.36.2 + github.com/openshift-eng/openshift-tests-extension v0.0.0-20260422120742-50dec0b5ecf3 github.com/openshift/api v0.0.0-20250513132935-9052dea86694 github.com/openshift/client-go v0.0.0-20250513150353-9ea84fa6431b github.com/openshift/cluster-api-actuator-pkg v0.0.0-20250401133953-e7e157c4c1fe @@ -324,6 +325,7 @@ replace ( k8s.io/dynamic-resource-allocation => github.com/openshift/kubernetes/staging/src/k8s.io/dynamic-resource-allocation v0.0.0-20250527023356-4cd5657aac98 k8s.io/endpointslice => github.com/openshift/kubernetes/staging/src/k8s.io/endpointslice v0.0.0-20250527023356-4cd5657aac98 k8s.io/externaljwt => github.com/openshift/kubernetes/staging/src/k8s.io/externaljwt v0.0.0-20250527023356-4cd5657aac98 + k8s.io/kms => github.com/openshift/kubernetes/staging/src/k8s.io/kms v0.0.0-20250527023356-4cd5657aac98 k8s.io/kube-aggregator => github.com/openshift/kubernetes/staging/src/k8s.io/kube-aggregator v0.0.0-20250527023356-4cd5657aac98 k8s.io/kube-controller-manager => github.com/openshift/kubernetes/staging/src/k8s.io/kube-controller-manager v0.0.0-20250527023356-4cd5657aac98 k8s.io/kube-proxy => github.com/openshift/kubernetes/staging/src/k8s.io/kube-proxy v0.0.0-20250527023356-4cd5657aac98 diff --git a/go.sum b/go.sum index 222359820..d4928fa3e 100644 --- a/go.sum +++ b/go.sum @@ -426,8 +426,8 @@ github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.11.1 h1:nHFvthhM0qY8/m+vfhJylliSshm8G1jJ2jDMcgULaH8= github.com/opencontainers/selinux v1.11.1/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= -github.com/openshift-eng/openshift-tests-extension v0.0.0-20250220212757-b9c4d98a0c45 h1:hXpbYtP3iTh8oy/RKwKkcMziwchY3fIk95ciczf7cOA= -github.com/openshift-eng/openshift-tests-extension v0.0.0-20250220212757-b9c4d98a0c45/go.mod h1:6gkP5f2HL0meusT0Aim8icAspcD1cG055xxBZ9yC68M= +github.com/openshift-eng/openshift-tests-extension v0.0.0-20260422120742-50dec0b5ecf3 h1:8DsrRTrsWWFU89XhpZJVhPzAk32/rxtHhw8+kELNVRo= +github.com/openshift-eng/openshift-tests-extension v0.0.0-20260422120742-50dec0b5ecf3/go.mod h1:pHOS9c6BjZv91OkkHyIHAOWnYhxwcxWQkyYGEvPyUCE= github.com/openshift/api v0.0.0-20250513132935-9052dea86694 h1:kPnk1+m89LJHexYsTP+MVM9OgJLxcpUR3vRdMQNF66s= github.com/openshift/api v0.0.0-20250513132935-9052dea86694/go.mod h1:yk60tHAmHhtVpJQo3TwVYq2zpuP70iJIFDCmeKMIzPw= github.com/openshift/apiserver-library-go v0.0.0-20250127121756-dc9a973f14ce h1:w0Up6YV1APcn20v/1h5IfuToz96o2pVqZyjzbw0yotU= @@ -476,6 +476,8 @@ github.com/openshift/kubernetes/staging/src/k8s.io/dynamic-resource-allocation v github.com/openshift/kubernetes/staging/src/k8s.io/dynamic-resource-allocation v0.0.0-20250527023356-4cd5657aac98/go.mod h1:lHc2r9QqTP5jQ/qzHRjEjvq+P2pMIJxH3pW2OIeKGm4= github.com/openshift/kubernetes/staging/src/k8s.io/externaljwt v0.0.0-20250527023356-4cd5657aac98 h1:UCG7qLSuT0WhLVFrhmeOD6lhTQJsGKQEoVYz22GOyxc= github.com/openshift/kubernetes/staging/src/k8s.io/externaljwt v0.0.0-20250527023356-4cd5657aac98/go.mod h1:cYoh1BjUit+0mmm1QFiHHIvFRp5NuJ/PtXFjqU6lG2I= +github.com/openshift/kubernetes/staging/src/k8s.io/kms v0.0.0-20250527023356-4cd5657aac98 h1:JLBBDOj3UuSLVqZfN+m6woQlSty9ITVhMIHLr61NVPc= +github.com/openshift/kubernetes/staging/src/k8s.io/kms v0.0.0-20250527023356-4cd5657aac98/go.mod h1:WwxOP90Kv6tPghVc5qa01Rt3aX5eU5SQMUJ8duaa6tE= github.com/openshift/kubernetes/staging/src/k8s.io/kube-aggregator v0.0.0-20250527023356-4cd5657aac98 h1:tWb8KZY4OHiKobRp6hhc37V+kv8rClXjexhTn+2RxS8= github.com/openshift/kubernetes/staging/src/k8s.io/kube-aggregator v0.0.0-20250527023356-4cd5657aac98/go.mod h1:0G+pIl5ZSz2OJTbaNrYgYDas3AAuLuyb6f7yp67vUc0= github.com/openshift/kubernetes/staging/src/k8s.io/kube-scheduler v0.0.0-20250527023356-4cd5657aac98 h1:GiO9lU3FaQdI2jYW8My1ZN/ZRcNdfekuK7EgCUO1+H8= @@ -836,8 +838,6 @@ k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kms v0.32.1 h1:TW6cswRI/fawoQRFGWLmEceO37rZXupdoRdmO019jCc= -k8s.io/kms v0.32.1/go.mod h1:Bk2evz/Yvk0oVrvm4MvZbgq8BD34Ksxs2SRHn4/UiOM= k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= diff --git a/test/extended/openstack/bz_2022627.go b/test/extended/openstack/bz_2022627.go index d8d7e9481..71446ed9a 100644 --- a/test/extended/openstack/bz_2022627.go +++ b/test/extended/openstack/bz_2022627.go @@ -22,7 +22,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/servers" ) -var _ = g.Describe("[sig-installer][Suite:openshift/openstack] Bugfix", func() { +var _ = g.Describe("[OTP][sig-installer][Suite:openshift/openstack] Bugfix", func() { defer g.GinkgoRecover() // returns the list of addresses diff --git a/test/extended/openstack/bz_2073398.go b/test/extended/openstack/bz_2073398.go index b540e0de6..0d47ee4a7 100644 --- a/test/extended/openstack/bz_2073398.go +++ b/test/extended/openstack/bz_2073398.go @@ -28,7 +28,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/subnets" ) -var _ = g.Describe("[sig-installer][Suite:openshift/openstack] Bugfix", func() { +var _ = g.Describe("[OTP][sig-installer][Suite:openshift/openstack] Bugfix", func() { defer g.GinkgoRecover() var dc dynamic.Interface diff --git a/test/extended/openstack/cloud-provider-config.go b/test/extended/openstack/cloud-provider-config.go index deb4a6cc7..f68e3aef2 100644 --- a/test/extended/openstack/cloud-provider-config.go +++ b/test/extended/openstack/cloud-provider-config.go @@ -18,7 +18,7 @@ import ( e2e "k8s.io/kubernetes/test/e2e/framework" ) -var _ = g.Describe("[sig-installer][Suite:openshift/openstack] The Openshift", func() { +var _ = g.Describe("[OTP][sig-installer][Suite:openshift/openstack] The Openshift", func() { defer g.GinkgoRecover() var err error diff --git a/test/extended/openstack/cpms.go b/test/extended/openstack/cpms.go index 9c2ee074c..e3a0cf454 100644 --- a/test/extended/openstack/cpms.go +++ b/test/extended/openstack/cpms.go @@ -18,7 +18,7 @@ import ( e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" ) -var _ = g.Describe("[sig-installer][Suite:openshift/openstack] ControlPlane MachineSet", func() { +var _ = g.Describe("[OTP][sig-installer][Suite:openshift/openstack] ControlPlane MachineSet", func() { defer g.GinkgoRecover() var dc dynamic.Interface diff --git a/test/extended/openstack/dualstack.go b/test/extended/openstack/dualstack.go index 468cb5e96..4188ce0c6 100644 --- a/test/extended/openstack/dualstack.go +++ b/test/extended/openstack/dualstack.go @@ -24,7 +24,7 @@ import ( e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" ) -var _ = g.Describe("[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform", func() { +var _ = g.Describe("[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform", func() { oc := exutil.NewCLI("openstack") var loadBalancerClient *gophercloud.ServiceClient var networkClient *gophercloud.ServiceClient diff --git a/test/extended/openstack/egressip.go b/test/extended/openstack/egressip.go index 6e2720e58..a99000a68 100644 --- a/test/extended/openstack/egressip.go +++ b/test/extended/openstack/egressip.go @@ -35,7 +35,7 @@ const ( egressAssignableLabelKey = "k8s.ovn.org/egress-assignable" ) -var _ = g.Describe("[sig-installer][Suite:openshift/openstack][egressip] An egressIP", func() { +var _ = g.Describe("[OTP][sig-installer][Suite:openshift/openstack][egressip] An egressIP", func() { var networkClient *gophercloud.ServiceClient var clientSet *kubernetes.Clientset var err error diff --git a/test/extended/openstack/kuryr.go b/test/extended/openstack/kuryr.go index 67ec01fb1..1c64333a5 100644 --- a/test/extended/openstack/kuryr.go +++ b/test/extended/openstack/kuryr.go @@ -11,7 +11,7 @@ import ( e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" ) -var _ = g.Describe("[sig-installer][Suite:openshift/openstack][Kuryr] Kuryr", func() { +var _ = g.Describe("[OTP][sig-installer][Suite:openshift/openstack][Kuryr] Kuryr", func() { var clientSet *kubernetes.Clientset g.BeforeEach(func(ctx g.SpecContext) { diff --git a/test/extended/openstack/loadbalancers.go b/test/extended/openstack/loadbalancers.go index 81193e1b9..2b21f96fc 100644 --- a/test/extended/openstack/loadbalancers.go +++ b/test/extended/openstack/loadbalancers.go @@ -42,7 +42,7 @@ import ( e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" ) -var _ = g.Describe("[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform", func() { +var _ = g.Describe("[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform", func() { oc := exutil.NewCLI("openstack") var loadBalancerClient *gophercloud.ServiceClient var networkClient *gophercloud.ServiceClient diff --git a/test/extended/openstack/local-bd-etcd.go b/test/extended/openstack/local-bd-etcd.go index 64c49eaa1..e259a533f 100644 --- a/test/extended/openstack/local-bd-etcd.go +++ b/test/extended/openstack/local-bd-etcd.go @@ -18,7 +18,7 @@ import ( e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" ) -var _ = g.Describe("[sig-installer][Suite:openshift/openstack] The OpenShift cluster", func() { +var _ = g.Describe("[OTP][sig-installer][Suite:openshift/openstack] The OpenShift cluster", func() { defer g.GinkgoRecover() const minEtcdDiskSizeGiB = 10 diff --git a/test/extended/openstack/machine.go b/test/extended/openstack/machine.go index f91086cca..05e110edc 100644 --- a/test/extended/openstack/machine.go +++ b/test/extended/openstack/machine.go @@ -31,7 +31,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/networks" ) -var _ = g.Describe("[sig-installer][Suite:openshift/openstack] Machine", func() { +var _ = g.Describe("[OTP][sig-installer][Suite:openshift/openstack] Machine", func() { defer g.GinkgoRecover() var dc dynamic.Interface diff --git a/test/extended/openstack/machineset.go b/test/extended/openstack/machineset.go index c01f7daaa..ff52ca424 100644 --- a/test/extended/openstack/machineset.go +++ b/test/extended/openstack/machineset.go @@ -22,7 +22,7 @@ const ( machineSetOwningLabel = "machine.openshift.io/cluster-api-machineset" ) -var _ = g.Describe("[sig-installer][Suite:openshift/openstack] MachineSet", func() { +var _ = g.Describe("[OTP][sig-installer][Suite:openshift/openstack] MachineSet", func() { defer g.GinkgoRecover() var dc dynamic.Interface diff --git a/test/extended/openstack/observability.go b/test/extended/openstack/observability.go index 73b69c869..a1d2d3b6d 100644 --- a/test/extended/openstack/observability.go +++ b/test/extended/openstack/observability.go @@ -60,7 +60,7 @@ const ( )` ) -var _ = g.Describe("[sig-installer][Suite:openshift/openstack] Check shift-on-stack", func() { +var _ = g.Describe("[OTP][sig-installer][Suite:openshift/openstack] Check shift-on-stack", func() { defer g.GinkgoRecover() diff --git a/test/extended/openstack/ocpbug_1765.go b/test/extended/openstack/ocpbug_1765.go index 453749d9e..1e71eae02 100644 --- a/test/extended/openstack/ocpbug_1765.go +++ b/test/extended/openstack/ocpbug_1765.go @@ -35,7 +35,7 @@ import ( runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" ) -var _ = g.Describe("[sig-installer][Suite:openshift/openstack] Bugfix", func() { +var _ = g.Describe("[OTP][sig-installer][Suite:openshift/openstack] Bugfix", func() { defer g.GinkgoRecover() var dc dynamic.Interface diff --git a/test/extended/openstack/servergroup.go b/test/extended/openstack/servergroup.go index d568a49df..1460bfa80 100644 --- a/test/extended/openstack/servergroup.go +++ b/test/extended/openstack/servergroup.go @@ -25,7 +25,7 @@ import ( e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" ) -var _ = g.Describe("[sig-installer][Suite:openshift/openstack] The OpenStack platform", func() { +var _ = g.Describe("[OTP][sig-installer][Suite:openshift/openstack] The OpenStack platform", func() { defer g.GinkgoRecover() var computeClient *gophercloud.ServiceClient diff --git a/test/extended/openstack/topology.go b/test/extended/openstack/topology.go index 43c1c07af..2c2b477fb 100644 --- a/test/extended/openstack/topology.go +++ b/test/extended/openstack/topology.go @@ -26,7 +26,7 @@ import ( exutil "github.com/openshift/origin/test/extended/util" ) -var _ = g.Describe("[sig-installer][Suite:openshift/openstack] The OpenShift cluster", func() { +var _ = g.Describe("[OTP][sig-installer][Suite:openshift/openstack] The OpenShift cluster", func() { defer g.GinkgoRecover() var dc dynamic.Interface diff --git a/test/extended/openstack/volumes.go b/test/extended/openstack/volumes.go index 0a9d356ec..01fd8383b 100644 --- a/test/extended/openstack/volumes.go +++ b/test/extended/openstack/volumes.go @@ -29,7 +29,7 @@ import ( e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" ) -var _ = g.Describe("[sig-installer][Suite:openshift/openstack] The OpenStack platform", func() { +var _ = g.Describe("[OTP][sig-installer][Suite:openshift/openstack] The OpenStack platform", func() { defer g.GinkgoRecover() var dc dynamic.Interface diff --git a/test/extended/util/annotate/generated/zz_generated.annotations.go b/test/extended/util/annotate/generated/zz_generated.annotations.go index efb8e64a8..e271cee84 100644 --- a/test/extended/util/annotate/generated/zz_generated.annotations.go +++ b/test/extended/util/annotate/generated/zz_generated.annotations.go @@ -7,107 +7,107 @@ import ( ) var Annotations = map[string]string{ - "[sig-installer][Suite:openshift/openstack] Bugfix bz_2022627: Machine should report all openstack instance addresses": "", + "[OTP][sig-installer][Suite:openshift/openstack] Bugfix bz_2022627: Machine should report all openstack instance addresses": "", - "[sig-installer][Suite:openshift/openstack] Bugfix bz_2073398: [Serial] MachineSet scale-in does not leak OpenStack ports": "", + "[OTP][sig-installer][Suite:openshift/openstack] Bugfix bz_2073398: [Serial] MachineSet scale-in does not leak OpenStack ports": "", - "[sig-installer][Suite:openshift/openstack] Bugfix ocpbug_1765: [Serial] noAllowedAddressPairs on one port should not affect other ports": "", + "[OTP][sig-installer][Suite:openshift/openstack] Bugfix ocpbug_1765: [Serial] noAllowedAddressPairs on one port should not affect other ports": "", - "[sig-installer][Suite:openshift/openstack] Check shift-on-stack correlated metric in RHOSO prometheus": "", + "[OTP][sig-installer][Suite:openshift/openstack] Check shift-on-stack correlated metric in RHOSO prometheus": "", - "[sig-installer][Suite:openshift/openstack] Check shift-on-stack kube_node_info metric in RHOSO prometheus": "", + "[OTP][sig-installer][Suite:openshift/openstack] Check shift-on-stack kube_node_info metric in RHOSO prometheus": "", - "[sig-installer][Suite:openshift/openstack] Check shift-on-stack kube_persistentvolume_info metric in RHOSO prometheus": "", + "[OTP][sig-installer][Suite:openshift/openstack] Check shift-on-stack kube_persistentvolume_info metric in RHOSO prometheus": "", - "[sig-installer][Suite:openshift/openstack] ControlPlane MachineSet ProviderSpec template is correctly applied to Machines": "", + "[OTP][sig-installer][Suite:openshift/openstack] ControlPlane MachineSet ProviderSpec template is correctly applied to Machines": "", - "[sig-installer][Suite:openshift/openstack] ControlPlane MachineSet has role master": "", + "[OTP][sig-installer][Suite:openshift/openstack] ControlPlane MachineSet has role master": "", - "[sig-installer][Suite:openshift/openstack] ControlPlane MachineSet replica number corresponds to the number of Machines": "", + "[OTP][sig-installer][Suite:openshift/openstack] ControlPlane MachineSet replica number corresponds to the number of Machines": "", - "[sig-installer][Suite:openshift/openstack] Machine ProviderSpec is correctly applied to OpenStack instances": "", + "[OTP][sig-installer][Suite:openshift/openstack] Machine ProviderSpec is correctly applied to OpenStack instances": "", - "[sig-installer][Suite:openshift/openstack] Machine are in phase Running": "", + "[OTP][sig-installer][Suite:openshift/openstack] Machine are in phase Running": "", - "[sig-installer][Suite:openshift/openstack] MachineSet ProviderSpec template is correctly applied to Machines": "", + "[OTP][sig-installer][Suite:openshift/openstack] MachineSet ProviderSpec template is correctly applied to Machines": "", - "[sig-installer][Suite:openshift/openstack] MachineSet replica number corresponds to the number of Machines": "", + "[OTP][sig-installer][Suite:openshift/openstack] MachineSet replica number corresponds to the number of Machines": "", - "[sig-installer][Suite:openshift/openstack] The OpenShift cluster enables topology aware scheduling when compute and volume AZs are identical": "", + "[OTP][sig-installer][Suite:openshift/openstack] The OpenShift cluster enables topology aware scheduling when compute and volume AZs are identical": "", - "[sig-installer][Suite:openshift/openstack] The OpenShift cluster runs with etcd on ephemeral local block device": "", + "[OTP][sig-installer][Suite:openshift/openstack] The OpenShift cluster runs with etcd on ephemeral local block device": "", - "[sig-installer][Suite:openshift/openstack] The OpenShift cluster should allow the manual setting of enable_topology to false [Serial]": "", + "[OTP][sig-installer][Suite:openshift/openstack] The OpenShift cluster should allow the manual setting of enable_topology to false [Serial]": "", - "[sig-installer][Suite:openshift/openstack] The OpenStack platform creates Control plane nodes in a server group": "", + "[OTP][sig-installer][Suite:openshift/openstack] The OpenStack platform creates Control plane nodes in a server group": "", - "[sig-installer][Suite:openshift/openstack] The OpenStack platform creates Control plane nodes on separate hosts when serverGroupPolicy is anti-affinity": "", + "[OTP][sig-installer][Suite:openshift/openstack] The OpenStack platform creates Control plane nodes on separate hosts when serverGroupPolicy is anti-affinity": "", - "[sig-installer][Suite:openshift/openstack] The OpenStack platform creates Worker nodes in a server group": "", + "[OTP][sig-installer][Suite:openshift/openstack] The OpenStack platform creates Worker nodes in a server group": "", - "[sig-installer][Suite:openshift/openstack] The OpenStack platform creates Worker nodes on separate hosts when serverGroupPolicy is anti-affinity": "", + "[OTP][sig-installer][Suite:openshift/openstack] The OpenStack platform creates Worker nodes on separate hosts when serverGroupPolicy is anti-affinity": "", - "[sig-installer][Suite:openshift/openstack] The OpenStack platform on volume creation should create a cinder volume when using cinder default storage class": "", + "[OTP][sig-installer][Suite:openshift/openstack] The OpenStack platform on volume creation should create a cinder volume when using cinder default storage class": "", - "[sig-installer][Suite:openshift/openstack] The OpenStack platform on volume creation should create a manila share when using manila storage class": "", + "[OTP][sig-installer][Suite:openshift/openstack] The OpenStack platform on volume creation should create a manila share when using manila storage class": "", - "[sig-installer][Suite:openshift/openstack] The OpenStack platform on volume creation should follow PVC specs during resizing for prometheus": "", + "[OTP][sig-installer][Suite:openshift/openstack] The OpenStack platform on volume creation should follow PVC specs during resizing for prometheus": "", - "[sig-installer][Suite:openshift/openstack] The Openshift on cloud provider configuration should haul the user config to the expected config maps": "", + "[OTP][sig-installer][Suite:openshift/openstack] The Openshift on cloud provider configuration should haul the user config to the expected config maps": "", - "[sig-installer][Suite:openshift/openstack] The Openshift on cloud provider configuration should set enabled property in [LoadBalancer] section in CCM depending on the NetworkType": "", + "[OTP][sig-installer][Suite:openshift/openstack] The Openshift on cloud provider configuration should set enabled property in [LoadBalancer] section in CCM depending on the NetworkType": "", - "[sig-installer][Suite:openshift/openstack] The Openshift on cloud provider configuration should store cloud credentials on secrets": "", + "[OTP][sig-installer][Suite:openshift/openstack] The Openshift on cloud provider configuration should store cloud credentials on secrets": "", - "[sig-installer][Suite:openshift/openstack][Kuryr] Kuryr should create a subnet for a namespace only when a pod without hostNetwork is created in the namespace": "", + "[OTP][sig-installer][Suite:openshift/openstack][Kuryr] Kuryr should create a subnet for a namespace only when a pod without hostNetwork is created in the namespace": "", - "[sig-installer][Suite:openshift/openstack][egressip] An egressIP attached to a floating IP should be kept after EgressIP node failover with OVN-Kubernetes NetworkType": "", + "[OTP][sig-installer][Suite:openshift/openstack][egressip] An egressIP attached to a floating IP should be kept after EgressIP node failover with OVN-Kubernetes NetworkType": "", - "[sig-installer][Suite:openshift/openstack][egressip] An egressIP with IPv6 format should be created on dualstack or ssipv6 cluster with OVN-Kubernetes NetworkType and dhcpv6-stateful mode": "", + "[OTP][sig-installer][Suite:openshift/openstack][egressip] An egressIP with IPv6 format should be created on dualstack or ssipv6 cluster with OVN-Kubernetes NetworkType and dhcpv6-stateful mode": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should apply lb-method on TCP Amphora LoadBalancer when a TCP svc with monitors and ETP:Local is created on Openshift": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should apply lb-method on TCP Amphora LoadBalancer when a TCP svc with monitors and ETP:Local is created on Openshift": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should apply lb-method on TCP OVN LoadBalancer when a TCP svc with monitors and ETP:Local is created on Openshift": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should apply lb-method on TCP OVN LoadBalancer when a TCP svc with monitors and ETP:Local is created on Openshift": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should apply lb-method on UDP Amphora LoadBalancer when a UDP svc with monitors and ETP:Local is created on Openshift": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should apply lb-method on UDP Amphora LoadBalancer when a UDP svc with monitors and ETP:Local is created on Openshift": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should apply lb-method on UDP OVN LoadBalancer when a UDP svc with monitors and ETP:Local is created on Openshift": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should apply lb-method on UDP OVN LoadBalancer when a UDP svc with monitors and ETP:Local is created on Openshift": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a TCP Amphora LoadBalancer when LoadBalancerService ingressController is created on Openshift": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a TCP Amphora LoadBalancer when LoadBalancerService ingressController is created on Openshift": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a TCP Amphora LoadBalancer when a TCP svc with type:LoadBalancer and IP family policy PreferDualStack and IP families [IPv4 IPv6] is created on Openshift": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a TCP Amphora LoadBalancer when a TCP svc with type:LoadBalancer and IP family policy PreferDualStack and IP families [IPv4 IPv6] is created on Openshift": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a TCP Amphora LoadBalancer when a TCP svc with type:LoadBalancer and IP family policy PreferDualStack and IP families [IPv6 IPv4] is created on Openshift": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a TCP Amphora LoadBalancer when a TCP svc with type:LoadBalancer and IP family policy PreferDualStack and IP families [IPv6 IPv4] is created on Openshift": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a TCP Amphora LoadBalancer when a TCP svc with type:LoadBalancer is created on Openshift": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a TCP Amphora LoadBalancer when a TCP svc with type:LoadBalancer is created on Openshift": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a TCP OVN LoadBalancer when LoadBalancerService ingressController is created on Openshift": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a TCP OVN LoadBalancer when LoadBalancerService ingressController is created on Openshift": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a TCP OVN LoadBalancer when a TCP svc with type:LoadBalancer and IP family policy PreferDualStack and IP families [IPv4 IPv6] is created on Openshift": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a TCP OVN LoadBalancer when a TCP svc with type:LoadBalancer and IP family policy PreferDualStack and IP families [IPv4 IPv6] is created on Openshift": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a TCP OVN LoadBalancer when a TCP svc with type:LoadBalancer and IP family policy PreferDualStack and IP families [IPv6 IPv4] is created on Openshift": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a TCP OVN LoadBalancer when a TCP svc with type:LoadBalancer and IP family policy PreferDualStack and IP families [IPv6 IPv4] is created on Openshift": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a TCP OVN LoadBalancer when a TCP svc with type:LoadBalancer is created on Openshift": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a TCP OVN LoadBalancer when a TCP svc with type:LoadBalancer is created on Openshift": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a UDP Amphora LoadBalancer when a UDP svc with type:LoadBalancer is created on Openshift": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a UDP Amphora LoadBalancer when a UDP svc with type:LoadBalancer is created on Openshift": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a UDP OVN LoadBalancer when a UDP svc with type:LoadBalancer is created on Openshift": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create a UDP OVN LoadBalancer when a UDP svc with type:LoadBalancer is created on Openshift": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create an UDP Amphora LoadBalancer using a pre-created FIP when an UDP LoadBalancer svc setting the LoadBalancerIP spec is created on Openshift": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create an UDP Amphora LoadBalancer using a pre-created FIP when an UDP LoadBalancer svc setting the LoadBalancerIP spec is created on Openshift": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create an UDP OVN LoadBalancer using a pre-created FIP when an UDP LoadBalancer svc setting the LoadBalancerIP spec is created on Openshift": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should create an UDP OVN LoadBalancer using a pre-created FIP when an UDP LoadBalancer svc setting the LoadBalancerIP spec is created on Openshift": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should limit service access on an UDP Amphora LoadBalancer when an UDP LoadBalancer svc setting the loadBalancerSourceRanges spec is created on Openshift": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should limit service access on an UDP Amphora LoadBalancer when an UDP LoadBalancer svc setting the loadBalancerSourceRanges spec is created on Openshift": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should limit service access on an UDP OVN LoadBalancer when an UDP LoadBalancer svc setting the loadBalancerSourceRanges spec is created on Openshift": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should limit service access on an UDP OVN LoadBalancer when an UDP LoadBalancer svc setting the loadBalancerSourceRanges spec is created on Openshift": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should not re-use an existing UDP Amphora LoadBalancer if shared internal svc is created": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should not re-use an existing UDP Amphora LoadBalancer if shared internal svc is created": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should not re-use an existing UDP OVN LoadBalancer if shared internal svc is created": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should not re-use an existing UDP OVN LoadBalancer if shared internal svc is created": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should re-use an existing UDP Amphora LoadBalancer when new svc is created on Openshift with the proper annotation": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should re-use an existing UDP Amphora LoadBalancer when new svc is created on Openshift with the proper annotation": "", - "[sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should re-use an existing UDP OVN LoadBalancer when new svc is created on Openshift with the proper annotation": "", + "[OTP][sig-installer][Suite:openshift/openstack][lb][Serial] The Openstack platform should re-use an existing UDP OVN LoadBalancer when new svc is created on Openshift with the proper annotation": "", } func init() { diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/LICENSE b/vendor/github.com/openshift-eng/openshift-tests-extension/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmd.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmd.go new file mode 100644 index 000000000..2db8cfa6e --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmd.go @@ -0,0 +1,23 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdimages" + "github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdinfo" + "github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdlist" + "github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun" + "github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdupdate" + "github.com/openshift-eng/openshift-tests-extension/pkg/extension" +) + +func DefaultExtensionCommands(registry *extension.Registry) []*cobra.Command { + return []*cobra.Command{ + cmdrun.NewRunSuiteCommand(registry), + cmdrun.NewRunTestCommand(registry), + cmdlist.NewListCommand(registry), + cmdinfo.NewInfoCommand(registry), + cmdupdate.NewUpdateCommand(registry), + cmdimages.NewImagesCommand(registry), + } +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdimages/cmdimages.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdimages/cmdimages.go new file mode 100644 index 000000000..33b458fac --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdimages/cmdimages.go @@ -0,0 +1,36 @@ +package cmdimages + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/openshift-eng/openshift-tests-extension/pkg/extension" + "github.com/openshift-eng/openshift-tests-extension/pkg/flags" +) + +func NewImagesCommand(registry *extension.Registry) *cobra.Command { + componentFlags := flags.NewComponentFlags() + + cmd := &cobra.Command{ + Use: "images", + Short: "List test images", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + extension := registry.Get(componentFlags.Component) + if extension == nil { + return fmt.Errorf("couldn't find the component %q", componentFlags.Component) + } + images, err := json.Marshal(extension.Images) + if err != nil { + return err + } + fmt.Fprintf(os.Stdout, "%s\n", images) + return nil + }, + } + componentFlags.BindFlags(cmd.Flags()) + return cmd +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdinfo/info.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdinfo/info.go new file mode 100644 index 000000000..1d4237876 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdinfo/info.go @@ -0,0 +1,38 @@ +package cmdinfo + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/openshift-eng/openshift-tests-extension/pkg/extension" + "github.com/openshift-eng/openshift-tests-extension/pkg/flags" +) + +func NewInfoCommand(registry *extension.Registry) *cobra.Command { + componentFlags := flags.NewComponentFlags() + + cmd := &cobra.Command{ + Use: "info", + Short: "Display extension metadata", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + extension := registry.Get(componentFlags.Component) + if extension == nil { + return fmt.Errorf("couldn't find the component %q", componentFlags.Component) + } + + info, err := json.MarshalIndent(extension, "", " ") + if err != nil { + return err + } + + fmt.Fprintf(os.Stdout, "%s\n", string(info)) + return nil + }, + } + componentFlags.BindFlags(cmd.Flags()) + return cmd +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdlist/list.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdlist/list.go new file mode 100644 index 000000000..31a040b7c --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdlist/list.go @@ -0,0 +1,133 @@ +package cmdlist + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/openshift-eng/openshift-tests-extension/pkg/extension" + "github.com/openshift-eng/openshift-tests-extension/pkg/flags" +) + +func NewListCommand(registry *extension.Registry) *cobra.Command { + opts := struct { + componentFlags *flags.ComponentFlags + suiteFlags *flags.SuiteFlags + outputFlags *flags.OutputFlags + environmentalFlags *flags.EnvironmentalFlags + }{ + suiteFlags: flags.NewSuiteFlags(), + componentFlags: flags.NewComponentFlags(), + outputFlags: flags.NewOutputFlags(), + environmentalFlags: flags.NewEnvironmentalFlags(), + } + + // Tests + listTestsCmd := &cobra.Command{ + Use: "tests", + Short: "List available tests", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + ext := registry.Get(opts.componentFlags.Component) + if ext == nil { + return fmt.Errorf("component not found: %s", opts.componentFlags.Component) + } + + // Find suite, if specified + var foundSuite *extension.Suite + var err error + if opts.suiteFlags.Suite != "" { + foundSuite, err = ext.GetSuite(opts.suiteFlags.Suite) + if err != nil { + return err + } + } + + // Filter for suite + specs := ext.GetSpecs() + if foundSuite != nil { + specs, err = specs.Filter(foundSuite.Qualifiers) + if err != nil { + return err + } + } + + specs, err = specs.FilterByEnvironment(*opts.environmentalFlags) + if err != nil { + return err + } + + data, err := opts.outputFlags.Marshal(specs) + if err != nil { + return err + } + fmt.Fprintf(os.Stdout, "%s\n", string(data)) + return nil + }, + } + opts.suiteFlags.BindFlags(listTestsCmd.Flags()) + opts.componentFlags.BindFlags(listTestsCmd.Flags()) + opts.environmentalFlags.BindFlags(listTestsCmd.Flags()) + opts.outputFlags.BindFlags(listTestsCmd.Flags()) + + // Suites + listSuitesCommand := &cobra.Command{ + Use: "suites", + Short: "List available suites", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + ext := registry.Get(opts.componentFlags.Component) + if ext == nil { + return fmt.Errorf("component not found: %s", opts.componentFlags.Component) + } + + suites := ext.Suites + + data, err := opts.outputFlags.Marshal(suites) + if err != nil { + return err + } + fmt.Fprintf(os.Stdout, "%s\n", string(data)) + return nil + }, + } + opts.componentFlags.BindFlags(listSuitesCommand.Flags()) + opts.outputFlags.BindFlags(listSuitesCommand.Flags()) + + // Components + listComponentsCmd := &cobra.Command{ + Use: "components", + Short: "List available components", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + var components []*extension.Component + registry.Walk(func(e *extension.Extension) { + components = append(components, &e.Component) + }) + + data, err := opts.outputFlags.Marshal(components) + if err != nil { + return err + } + fmt.Fprintf(os.Stdout, "%s\n", string(data)) + return nil + }, + } + opts.outputFlags.BindFlags(listComponentsCmd.Flags()) + + var listCmd = &cobra.Command{ + Use: "list [subcommand]", + Short: "List items", + RunE: func(cmd *cobra.Command, args []string) error { + return listTestsCmd.RunE(cmd, args) + }, + } + opts.suiteFlags.BindFlags(listCmd.Flags()) + opts.componentFlags.BindFlags(listCmd.Flags()) + opts.outputFlags.BindFlags(listCmd.Flags()) + opts.environmentalFlags.BindFlags(listCmd.Flags()) + listCmd.AddCommand(listTestsCmd, listComponentsCmd, listSuitesCommand) + + return listCmd +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun/runsuite.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun/runsuite.go new file mode 100644 index 000000000..a30b05e86 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun/runsuite.go @@ -0,0 +1,161 @@ +package cmdrun + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "os/signal" + "path/filepath" + "syscall" + "time" + + "github.com/spf13/cobra" + + "github.com/openshift-eng/openshift-tests-extension/pkg/extension" + "github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests" + "github.com/openshift-eng/openshift-tests-extension/pkg/flags" +) + +func NewRunSuiteCommand(registry *extension.Registry) *cobra.Command { + opts := struct { + componentFlags *flags.ComponentFlags + outputFlags *flags.OutputFlags + concurrencyFlags *flags.ConcurrencyFlags + junitPath string + htmlPath string + }{ + componentFlags: flags.NewComponentFlags(), + outputFlags: flags.NewOutputFlags(), + concurrencyFlags: flags.NewConcurrencyFlags(), + junitPath: "", + htmlPath: "", + } + + cmd := &cobra.Command{ + Use: "run-suite NAME", + Short: "Run a group of tests by suite. This is more limited than origin, and intended for light local " + + "development use. Orchestration parameters, scheduling, isolation, etc are not obeyed, and Ginkgo tests are executed serially.", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + ctx, cancelCause := context.WithCancelCause(context.Background()) + defer cancelCause(errors.New("exiting")) + + abortCh := make(chan os.Signal, 2) + go func() { + <-abortCh + fmt.Fprintf(os.Stderr, "Interrupted, terminating tests") + cancelCause(errors.New("interrupt received")) + + select { + case sig := <-abortCh: + fmt.Fprintf(os.Stderr, "Interrupted twice, exiting (%s)", sig) + switch sig { + case syscall.SIGINT: + os.Exit(130) + default: + os.Exit(130) // if we were interrupted, never return zero. + } + + case <-time.After(30 * time.Minute): // allow time for cleanup. If we finish before this, we'll exit + fmt.Fprintf(os.Stderr, "Timed out during cleanup, exiting") + os.Exit(130) // if we were interrupted, never return zero. + } + }() + signal.Notify(abortCh, syscall.SIGINT, syscall.SIGTERM) + + ext := registry.Get(opts.componentFlags.Component) + if ext == nil { + return fmt.Errorf("component not found: %s", opts.componentFlags.Component) + } + if len(args) != 1 { + return fmt.Errorf("must specify one suite name") + } + suite, err := ext.GetSuite(args[0]) + if err != nil { + return fmt.Errorf("couldn't find suite %q: %w", args[0], err) + } + + compositeWriter := extensiontests.NewCompositeResultWriter() + defer func() { + if err = compositeWriter.Flush(); err != nil { + fmt.Fprintf(os.Stderr, "failed to write results: %v\n", err) + } + }() + + // JUnit writer if needed + if opts.junitPath != "" { + junitWriter, err := extensiontests.NewJUnitResultWriter(opts.junitPath, suite.Name) + if err != nil { + return fmt.Errorf("couldn't create junit writer: %w", err) + } + compositeWriter.AddWriter(junitWriter) + } + // HTML writer if needed + if opts.htmlPath != "" { + htmlWriter, err := extensiontests.NewHTMLResultWriter(opts.htmlPath, suite.Name) + if err != nil { + return fmt.Errorf("couldn't create html writer: %w", err) + } + compositeWriter.AddWriter(htmlWriter) + } + + // JSON writer + jsonWriter, err := extensiontests.NewJSONResultWriter(os.Stdout, + extensiontests.ResultFormat(opts.outputFlags.Output)) + if err != nil { + return err + } + compositeWriter.AddWriter(jsonWriter) + + specs, err := ext.GetSpecs().Filter(suite.Qualifiers) + if err != nil { + return fmt.Errorf("couldn't filter specs: %w", err) + } + + if suite.TestTimeout != nil { + for _, spec := range specs { + if spec.Timeout == 0 { + spec.Timeout = *suite.TestTimeout + } + } + } + + concurrency := opts.concurrencyFlags.MaxConcurency + if suite.Parallelism > 0 { + concurrency = min(concurrency, suite.Parallelism) + } + results, runErr := specs.Run(ctx, compositeWriter, concurrency) + if opts.junitPath != "" { + // we want to commit the results to disk regardless of the success or failure of the specs + if err := writeResults(opts.junitPath, results); err != nil { + fmt.Fprintf(os.Stderr, "Failed to write test results to disk: %v\n", err) + } + } + return runErr + }, + } + opts.componentFlags.BindFlags(cmd.Flags()) + opts.outputFlags.BindFlags(cmd.Flags()) + opts.concurrencyFlags.BindFlags(cmd.Flags()) + cmd.Flags().StringVarP(&opts.junitPath, "junit-path", "j", opts.junitPath, "write results to junit XML") + cmd.Flags().StringVar(&opts.htmlPath, "html-path", opts.htmlPath, "write results to summary HTML") + + return cmd +} + +func writeResults(jUnitPath string, results []*extensiontests.ExtensionTestResult) error { + jUnitDir := filepath.Dir(jUnitPath) + if err := os.MkdirAll(jUnitDir, 0755); err != nil { + return fmt.Errorf("failed to create output directory: %v", err) + } + + encodedResults, err := json.MarshalIndent(results, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal results: %v", err) + } + + outputPath := filepath.Join(jUnitDir, fmt.Sprintf("extension_test_result_e2e_%s.json", time.Now().UTC().Format("20060102-150405"))) + return os.WriteFile(outputPath, encodedResults, 0644) +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun/runtest.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun/runtest.go new file mode 100644 index 000000000..c62021e7e --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun/runtest.go @@ -0,0 +1,113 @@ +package cmdrun + +import ( + "bufio" + "context" + "errors" + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "github.com/spf13/cobra" + + "github.com/openshift-eng/openshift-tests-extension/pkg/extension" + "github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests" + "github.com/openshift-eng/openshift-tests-extension/pkg/flags" +) + +func NewRunTestCommand(registry *extension.Registry) *cobra.Command { + opts := struct { + componentFlags *flags.ComponentFlags + concurrencyFlags *flags.ConcurrencyFlags + nameFlags *flags.NamesFlags + outputFlags *flags.OutputFlags + }{ + componentFlags: flags.NewComponentFlags(), + nameFlags: flags.NewNamesFlags(), + outputFlags: flags.NewOutputFlags(), + concurrencyFlags: flags.NewConcurrencyFlags(), + } + + cmd := &cobra.Command{ + Use: "run-test [-n NAME...] [NAME]", + Short: "Runs tests by name", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + ctx, cancelCause := context.WithCancelCause(context.Background()) + defer cancelCause(errors.New("exiting")) + + abortCh := make(chan os.Signal, 2) + go func() { + <-abortCh + fmt.Fprintf(os.Stderr, "Interrupted, terminating tests") + cancelCause(errors.New("interrupt received")) + + select { + case sig := <-abortCh: + fmt.Fprintf(os.Stderr, "Interrupted twice, exiting (%s)", sig) + switch sig { + case syscall.SIGINT: + os.Exit(130) + default: + os.Exit(130) // if we were interrupted, never return zero. + } + + case <-time.After(30 * time.Minute): // allow time for cleanup. If we finish before this, we'll exit + fmt.Fprintf(os.Stderr, "Timed out during cleanup, exiting") + os.Exit(130) // if we were interrupted, never return zero. + } + }() + signal.Notify(abortCh, syscall.SIGINT, syscall.SIGTERM) + + ext := registry.Get(opts.componentFlags.Component) + if ext == nil { + return fmt.Errorf("component not found: %s", opts.componentFlags.Component) + } + if len(args) > 1 { + return fmt.Errorf("use --names to specify more than one test") + } + opts.nameFlags.Names = append(opts.nameFlags.Names, args...) + + // allow reading tests from an stdin pipe + info, err := os.Stdin.Stat() + if err != nil { + return err + } + if info.Mode()&os.ModeCharDevice == 0 { // Check if input is from a pipe + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + opts.nameFlags.Names = append(opts.nameFlags.Names, scanner.Text()) + } + if err := scanner.Err(); err != nil { + return fmt.Errorf("error reading from stdin: %v", err) + } + } + + if len(opts.nameFlags.Names) == 0 { + return fmt.Errorf("must specify at least one test") + } + + specs, err := ext.FindSpecsByName(opts.nameFlags.Names...) + if err != nil { + return err + } + + w, err := extensiontests.NewJSONResultWriter(os.Stdout, extensiontests.ResultFormat(opts.outputFlags.Output)) + if err != nil { + return err + } + defer w.Flush() + + _, err = specs.Run(ctx, w, opts.concurrencyFlags.MaxConcurency) + return err + }, + } + opts.componentFlags.BindFlags(cmd.Flags()) + opts.nameFlags.BindFlags(cmd.Flags()) + opts.outputFlags.BindFlags(cmd.Flags()) + opts.concurrencyFlags.BindFlags(cmd.Flags()) + + return cmd +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdupdate/update.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdupdate/update.go new file mode 100644 index 000000000..5d847308e --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdupdate/update.go @@ -0,0 +1,84 @@ +package cmdupdate + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + + "github.com/openshift-eng/openshift-tests-extension/pkg/extension" + "github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests" + "github.com/openshift-eng/openshift-tests-extension/pkg/flags" +) + +const metadataDirectory = ".openshift-tests-extension" + +// NewUpdateCommand adds an "update" command used to generate and verify the metadata we keep track of. This should +// be a black box to end users, i.e. we can add more criteria later they'll consume when revendoring. For now, +// we prevent a test to be renamed without updating other names, or a test to be deleted. +func NewUpdateCommand(registry *extension.Registry) *cobra.Command { + componentFlags := flags.NewComponentFlags() + + cmd := &cobra.Command{ + Use: "update", + Short: "Update test metadata", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + ext := registry.Get(componentFlags.Component) + if ext == nil { + return fmt.Errorf("couldn't find the component %q", componentFlags.Component) + } + + // Create the metadata directory if it doesn't exist + if err := os.MkdirAll(metadataDirectory, 0755); err != nil { + return fmt.Errorf("failed to create directory %s: %w", metadataDirectory, err) + } + + // Read existing specs + metadataPath := filepath.Join(metadataDirectory, fmt.Sprintf("%s.json", strings.ReplaceAll(ext.Component.Identifier(), ":", "_"))) + var oldSpecs extensiontests.ExtensionTestSpecs + source, err := os.Open(metadataPath) + if err != nil { + if !os.IsNotExist(err) { + return fmt.Errorf("failed to open file: %s: %+w", metadataPath, err) + } + } else { + if err := json.NewDecoder(source).Decode(&oldSpecs); err != nil { + return fmt.Errorf("failed to decode file: %s: %+w", metadataPath, err) + } + + missing, err := ext.FindRemovedTestsWithoutRename(oldSpecs) + if err != nil && len(missing) > 0 { + fmt.Fprintf(os.Stderr, "Missing Tests:\n") + for _, name := range missing { + fmt.Fprintf(os.Stdout, " * %s\n", name) + } + fmt.Fprintf(os.Stderr, "\n") + + return fmt.Errorf("missing tests, if you've renamed tests you must add their names to OriginalName, " + + "or mark them obsolete") + } + } + + // no missing tests, write the results + newSpecs := ext.GetSpecs() + data, err := json.MarshalIndent(newSpecs, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal specs to JSON: %w", err) + } + + // Write the JSON data to the file + if err := os.WriteFile(metadataPath, data, 0644); err != nil { + return fmt.Errorf("failed to write file %s: %w", metadataPath, err) + } + + fmt.Printf("successfully updated metadata\n") + return nil + }, + } + componentFlags.BindFlags(cmd.Flags()) + return cmd +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/dbtime/time.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/dbtime/time.go new file mode 100644 index 000000000..b7651ba02 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/dbtime/time.go @@ -0,0 +1,26 @@ +package dbtime + +import "time" + +// DBTime is a type suitable for direct importing into databases like BigQuery, +// formatted like 2006-01-02 15:04:05.000000 UTC. +type DBTime time.Time + +func Ptr(t time.Time) *DBTime { + return (*DBTime)(&t) +} + +func (dbt *DBTime) MarshalJSON() ([]byte, error) { + formattedTime := time.Time(*dbt).Format(`"2006-01-02 15:04:05.000000 UTC"`) + return []byte(formattedTime), nil +} + +func (dbt *DBTime) UnmarshalJSON(b []byte) error { + timeStr := string(b[1 : len(b)-1]) + parsedTime, err := time.Parse("2006-01-02 15:04:05.000000 UTC", timeStr) + if err != nil { + return err + } + *dbt = (DBTime)(parsedTime) + return nil +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extension.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extension.go new file mode 100644 index 000000000..b9fbfb2ec --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extension.go @@ -0,0 +1,165 @@ +package extension + +import ( + "fmt" + "strings" + + et "github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests" + "github.com/openshift-eng/openshift-tests-extension/pkg/util/sets" + "github.com/openshift-eng/openshift-tests-extension/pkg/version" +) + +func NewExtension(product, kind, name string) *Extension { + return &Extension{ + APIVersion: CurrentExtensionAPIVersion, + Source: Source{ + Commit: version.CommitFromGit, + BuildDate: version.BuildDate, + GitTreeState: version.GitTreeState, + }, + Component: Component{ + Product: product, + Kind: kind, + Name: name, + }, + } +} + +func (e *Extension) GetSuite(name string) (*Suite, error) { + var suite *Suite + + for _, s := range e.Suites { + if s.Name == name { + suite = &s + break + } + } + + if suite == nil { + return nil, fmt.Errorf("no such suite: %s", name) + } + + return suite, nil +} + +func (e *Extension) GetSpecs() et.ExtensionTestSpecs { + return e.specs +} + +func (e *Extension) AddSpecs(specs et.ExtensionTestSpecs) { + specs.Walk(func(spec *et.ExtensionTestSpec) { + spec.Source = e.Component.Identifier() + }) + + e.specs = append(e.specs, specs...) +} + +// IgnoreObsoleteTests allows removal of a test. +func (e *Extension) IgnoreObsoleteTests(testNames ...string) { + if e.obsoleteTests == nil { + e.obsoleteTests = sets.New[string](testNames...) + } else { + e.obsoleteTests.Insert(testNames...) + } +} + +// FindRemovedTestsWithoutRename compares the current set of test specs against oldSpecs, including consideration of the original name, +// we return an error. Can be used to detect test renames or removals. +func (e *Extension) FindRemovedTestsWithoutRename(oldSpecs et.ExtensionTestSpecs) ([]string, error) { + currentSpecs := e.GetSpecs() + currentMap := make(map[string]bool) + + // Populate current specs into a map for quick lookup by both Name and OriginalName. + for _, spec := range currentSpecs { + currentMap[spec.Name] = true + if spec.OriginalName != "" { + currentMap[spec.OriginalName] = true + } + } + + var removedTests []string + + // Check oldSpecs against current specs. + for _, oldSpec := range oldSpecs { + // Skip if the test is marked as obsolete. + if e.obsoleteTests.Has(oldSpec.Name) { + continue + } + + // Check if oldSpec is missing in currentSpecs by both Name and OriginalName. + if !currentMap[oldSpec.Name] && (oldSpec.OriginalName == "" || !currentMap[oldSpec.OriginalName]) { + removedTests = append(removedTests, oldSpec.Name) + } + } + + // Return error if any removed tests were found. + if len(removedTests) > 0 { + return removedTests, fmt.Errorf("tests removed without rename: %v", removedTests) + } + + return nil, nil +} + +// AddGlobalSuite adds a suite whose qualifiers will apply to all tests, +// not just this one. Allowing a developer to create a composed suite of +// tests from many sources. +func (e *Extension) AddGlobalSuite(suite Suite) *Extension { + if e.Suites == nil { + e.Suites = []Suite{suite} + } else { + e.Suites = append(e.Suites, suite) + } + + return e +} + +// AddSuite adds a suite whose qualifiers will only apply to tests present +// in its own extension. +func (e *Extension) AddSuite(suite Suite) *Extension { + expr := fmt.Sprintf("source == %q", e.Component.Identifier()) + if len(suite.Qualifiers) == 0 { + suite.Qualifiers = []string{expr} + } else { + for i := range suite.Qualifiers { + suite.Qualifiers[i] = fmt.Sprintf("(%s) && (%s)", + expr, suite.Qualifiers[i]) + } + } + + e.AddGlobalSuite(suite) + return e +} + +func (e *Extension) RegisterImage(image Image) *Extension { + e.Images = append(e.Images, image) + return e +} + +func (e *Extension) FindSpecsByName(names ...string) (et.ExtensionTestSpecs, error) { + var specs et.ExtensionTestSpecs + var notFound []string + + for _, name := range names { + found := false + for i := range e.specs { + if e.specs[i].Name == name { + specs = append(specs, e.specs[i]) + found = true + break + } + } + if !found { + notFound = append(notFound, name) + } + } + + if len(notFound) > 0 { + return nil, fmt.Errorf("no such tests: %s", strings.Join(notFound, ", ")) + } + + return specs, nil +} + +func (e *Component) Identifier() string { + return fmt.Sprintf("%s:%s:%s", e.Product, e.Kind, e.Name) +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/environment.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/environment.go new file mode 100644 index 000000000..b5116a535 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/environment.go @@ -0,0 +1,92 @@ +package extensiontests + +import ( + "fmt" + "strings" +) + +func PlatformEquals(platform string) string { + return fmt.Sprintf(`platform=="%s"`, platform) +} + +func NetworkEquals(network string) string { + return fmt.Sprintf(`network=="%s"`, network) +} + +func NetworkStackEquals(networkStack string) string { + return fmt.Sprintf(`networkStack=="%s"`, networkStack) +} + +func UpgradeEquals(upgrade string) string { + return fmt.Sprintf(`upgrade=="%s"`, upgrade) +} + +func TopologyEquals(topology string) string { + return fmt.Sprintf(`topology=="%s"`, topology) +} + +func ArchitectureEquals(arch string) string { + return fmt.Sprintf(`architecture=="%s"`, arch) +} + +func APIGroupEnabled(apiGroup string) string { + return fmt.Sprintf(`apiGroups.exists(api, api=="%s")`, apiGroup) +} + +func APIGroupDisabled(apiGroup string) string { + return fmt.Sprintf(`!apiGroups.exists(api, api=="%s")`, apiGroup) +} + +func FeatureGateEnabled(featureGate string) string { + return fmt.Sprintf(`featureGates.exists(fg, fg=="%s")`, featureGate) +} + +func FeatureGateDisabled(featureGate string) string { + return fmt.Sprintf(`!featureGates.exists(fg, fg=="%s")`, featureGate) +} + +func ExternalConnectivityEquals(externalConnectivity string) string { + return fmt.Sprintf(`externalConnectivity=="%s"`, externalConnectivity) +} + +func OptionalCapabilitiesIncludeAny(optionalCapability ...string) string { + for i := range optionalCapability { + optionalCapability[i] = OptionalCapabilityExists(optionalCapability[i]) + } + return fmt.Sprintf("(%s)", fmt.Sprint(strings.Join(optionalCapability, " || "))) +} + +func OptionalCapabilitiesIncludeAll(optionalCapability ...string) string { + for i := range optionalCapability { + optionalCapability[i] = OptionalCapabilityExists(optionalCapability[i]) + } + return fmt.Sprintf("(%s)", fmt.Sprint(strings.Join(optionalCapability, " && "))) +} + +func OptionalCapabilityExists(optionalCapability string) string { + return fmt.Sprintf(`optionalCapabilities.exists(oc, oc=="%s")`, optionalCapability) +} + +func NoOptionalCapabilitiesExist() string { + return "size(optionalCapabilities) == 0" +} + +func InstallerEquals(installer string) string { + return fmt.Sprintf(`installer=="%s"`, installer) +} + +func VersionEquals(version string) string { + return fmt.Sprintf(`version=="%s"`, version) +} + +func FactEquals(key, value string) string { + return fmt.Sprintf(`(fact_keys.exists(k, k=="%s") && facts["%s"].matches("%s"))`, key, key, value) +} + +func Or(cel ...string) string { + return fmt.Sprintf("(%s)", strings.Join(cel, " || ")) +} + +func And(cel ...string) string { + return fmt.Sprintf("(%s)", strings.Join(cel, " && ")) +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/result.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/result.go new file mode 100644 index 000000000..9c03a0a84 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/result.go @@ -0,0 +1,125 @@ +package extensiontests + +import ( + "bytes" + _ "embed" + "encoding/json" + "fmt" + "strings" + "text/template" + + "github.com/openshift-eng/openshift-tests-extension/pkg/junit" +) + +func (results ExtensionTestResults) Walk(walkFn func(*ExtensionTestResult)) { + for i := range results { + walkFn(results[i]) + } +} + +// AddDetails adds additional information to an ExtensionTestResult. Value must marshal to JSON. +func (result *ExtensionTestResult) AddDetails(name string, value interface{}) { + result.Details = append(result.Details, Details{Name: name, Value: value}) +} + +func (result ExtensionTestResult) ToJUnit() *junit.TestCase { + tc := &junit.TestCase{ + Name: result.Name, + Duration: float64(result.Duration) / 1000.0, + } + switch result.Result { + case ResultFailed: + tc.FailureOutput = &junit.FailureOutput{ + Message: result.Error, + Output: result.Error, + } + case ResultSkipped: + messages := []string{} + for _, detail := range result.Details { + messages = append(messages, fmt.Sprintf("%s: %s", detail.Name, detail.Value)) + } + tc.SkipMessage = &junit.SkipMessage{ + Message: strings.Join(messages, "\n"), + } + case ResultPassed: + tc.SystemOut = result.Output + } + + return tc +} + +func (results ExtensionTestResults) ToJUnit(suiteName string) junit.TestSuite { + suite := junit.TestSuite{ + Name: suiteName, + } + + results.Walk(func(result *ExtensionTestResult) { + suite.NumTests++ + switch result.Result { + case ResultFailed: + suite.NumFailed++ + case ResultSkipped: + suite.NumSkipped++ + case ResultPassed: + // do nothing + default: + panic(fmt.Sprintf("unknown result type: %s", result.Result)) + } + + suite.TestCases = append(suite.TestCases, result.ToJUnit()) + }) + + return suite +} + +//go:embed viewer.html +var viewerHtml []byte + +// RenderResultsHTML renders the HTML viewer template with the provided JSON data. +// The caller is responsible for marshaling their results to JSON. This allows +// callers with different result struct types to use the same HTML viewer. +func RenderResultsHTML(jsonData []byte, suiteName string) ([]byte, error) { + tmpl, err := template.New("viewer").Parse(string(viewerHtml)) + if err != nil { + return nil, fmt.Errorf("failed to parse template: %w", err) + } + var out bytes.Buffer + if err := tmpl.Execute(&out, struct { + Data string + SuiteName string + }{ + string(jsonData), + suiteName, + }); err != nil { + return nil, fmt.Errorf("failed to execute template: %w", err) + } + return out.Bytes(), nil +} + +func (results ExtensionTestResults) ToHTML(suiteName string) ([]byte, error) { + encoded, err := json.Marshal(results) + if err != nil { + return nil, fmt.Errorf("failed to marshal extension test results: %w", err) + } + // pare down the output if there's a lot, we want this to load in some reasonable amount of time + if len(encoded) > 2<<20 { + // n.b. this is wasteful, but we want to mutate our inputs in a safe manner, so the encode/decode/encode + // pass is useful as a deep copy + var copiedResults ExtensionTestResults + if err := json.Unmarshal(encoded, &copiedResults); err != nil { + return nil, fmt.Errorf("failed to unmarshal extension test results: %w", err) + } + copiedResults.Walk(func(result *ExtensionTestResult) { + if result.Result == ResultPassed { + result.Error = "" + result.Output = "" + result.Details = nil + } + }) + encoded, err = json.Marshal(copiedResults) + if err != nil { + return nil, fmt.Errorf("failed to marshal extension test results: %w", err) + } + } + return RenderResultsHTML(encoded, suiteName) +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/result_writer.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/result_writer.go new file mode 100644 index 000000000..f9ca434ca --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/result_writer.go @@ -0,0 +1,213 @@ +package extensiontests + +import ( + "encoding/json" + "encoding/xml" + "errors" + "fmt" + "io" + "os" + "sync" + + "github.com/openshift-eng/openshift-tests-extension/pkg/junit" +) + +type ResultWriter interface { + Write(result *ExtensionTestResult) + Flush() error +} + +type NullResultWriter struct{} + +func (NullResultWriter) Write(*ExtensionTestResult) {} +func (NullResultWriter) Flush() error { return nil } + +type CompositeResultWriter struct { + writers []ResultWriter +} + +func NewCompositeResultWriter(writers ...ResultWriter) *CompositeResultWriter { + return &CompositeResultWriter{ + writers: writers, + } +} + +func (w *CompositeResultWriter) AddWriter(writer ResultWriter) { + w.writers = append(w.writers, writer) +} + +func (w *CompositeResultWriter) Write(res *ExtensionTestResult) { + for _, writer := range w.writers { + writer.Write(res) + } +} + +func (w *CompositeResultWriter) Flush() error { + var errs []error + for _, writer := range w.writers { + if err := writer.Flush(); err != nil { + errs = append(errs, err) + } + } + + return errors.Join(errs...) +} + +type JUnitResultWriter struct { + lock sync.Mutex + testSuite *junit.TestSuite + out *os.File + suiteName string + path string + results ExtensionTestResults +} + +func NewJUnitResultWriter(path, suiteName string) (ResultWriter, error) { + file, err := os.Create(path) + if err != nil { + return nil, err + } + + return &JUnitResultWriter{ + testSuite: &junit.TestSuite{ + Name: suiteName, + }, + out: file, + suiteName: suiteName, + path: path, + }, nil +} + +func (w *JUnitResultWriter) Write(res *ExtensionTestResult) { + w.lock.Lock() + defer w.lock.Unlock() + w.results = append(w.results, res) +} + +func (w *JUnitResultWriter) Flush() error { + w.lock.Lock() + defer w.lock.Unlock() + data, err := xml.MarshalIndent(w.results.ToJUnit(w.suiteName), "", " ") + if err != nil { + return fmt.Errorf("failed to marshal JUnit XML: %w", err) + } + if _, err := w.out.Write(data); err != nil { + return err + } + if err := w.out.Close(); err != nil { + return err + } + + return nil +} + +type ResultFormat string + +var ( + JSON ResultFormat = "json" + JSONL ResultFormat = "jsonl" +) + +type JSONResultWriter struct { + lock sync.Mutex + out io.Writer + format ResultFormat + results ExtensionTestResults +} + +func NewJSONResultWriter(out io.Writer, format ResultFormat) (*JSONResultWriter, error) { + switch format { + case JSON, JSONL: + // do nothing + default: + return nil, fmt.Errorf("unsupported result format: %s", format) + } + + return &JSONResultWriter{ + out: out, + format: format, + results: ExtensionTestResults{}, + }, nil +} + +func (w *JSONResultWriter) Write(result *ExtensionTestResult) { + w.lock.Lock() + defer w.lock.Unlock() + switch w.format { + case JSONL: + // JSONL gets written to out as we get the items + data, err := json.Marshal(result) + if err != nil { + panic(err) + } + fmt.Fprintf(w.out, "%s\n", string(data)) + case JSON: + w.results = append(w.results, result) + } +} + +func (w *JSONResultWriter) Flush() error { + w.lock.Lock() + defer w.lock.Unlock() + switch w.format { + case JSONL: + // we already wrote it out + case JSON: + data, err := json.MarshalIndent(w.results, "", " ") + if err != nil { + return err + } + _, err = w.out.Write(data) + return err + } + + return nil +} + +type HTMLResultWriter struct { + lock sync.Mutex + testSuite *junit.TestSuite + out *os.File + suiteName string + path string + results ExtensionTestResults +} + +func NewHTMLResultWriter(path, suiteName string) (ResultWriter, error) { + file, err := os.Create(path) + if err != nil { + return nil, err + } + + return &HTMLResultWriter{ + testSuite: &junit.TestSuite{ + Name: suiteName, + }, + out: file, + suiteName: suiteName, + path: path, + }, nil +} + +func (w *HTMLResultWriter) Write(res *ExtensionTestResult) { + w.lock.Lock() + defer w.lock.Unlock() + w.results = append(w.results, res) +} + +func (w *HTMLResultWriter) Flush() error { + w.lock.Lock() + defer w.lock.Unlock() + data, err := w.results.ToHTML(w.suiteName) + if err != nil { + return fmt.Errorf("failed to create result HTML: %w", err) + } + if _, err := w.out.Write(data); err != nil { + return err + } + if err := w.out.Close(); err != nil { + return err + } + + return nil +} \ No newline at end of file diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/spec.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/spec.go new file mode 100644 index 000000000..e87809c8a --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/spec.go @@ -0,0 +1,621 @@ +package extensiontests + +import ( + "context" + "fmt" + "os" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/google/cel-go/cel" + "github.com/google/cel-go/checker/decls" + "github.com/google/cel-go/common/types" + + "github.com/openshift-eng/openshift-tests-extension/pkg/dbtime" + "github.com/openshift-eng/openshift-tests-extension/pkg/flags" +) + +// Walk iterates over all test specs, and executions the function provided. The test spec can be mutated. +func (specs ExtensionTestSpecs) Walk(walkFn func(*ExtensionTestSpec)) ExtensionTestSpecs { + for i := range specs { + walkFn(specs[i]) + } + + return specs +} + +type SelectFunction func(spec *ExtensionTestSpec) bool + +// Select filters the ExtensionTestSpecs to only those that match the provided SelectFunction +func (specs ExtensionTestSpecs) Select(selectFn SelectFunction) ExtensionTestSpecs { + filtered := ExtensionTestSpecs{} + for _, spec := range specs { + if selectFn(spec) { + filtered = append(filtered, spec) + } + } + + return filtered +} + +// MustSelect filters the ExtensionTestSpecs to only those that match the provided SelectFunction. +// if no specs are selected, it will throw an error +func (specs ExtensionTestSpecs) MustSelect(selectFn SelectFunction) (ExtensionTestSpecs, error) { + filtered := specs.Select(selectFn) + if len(filtered) == 0 { + return filtered, fmt.Errorf("no specs selected with specified SelectFunctions") + } + + return filtered, nil +} + +// SelectAny filters the ExtensionTestSpecs to only those that match any of the provided SelectFunctions +func (specs ExtensionTestSpecs) SelectAny(selectFns []SelectFunction) ExtensionTestSpecs { + filtered := ExtensionTestSpecs{} + for _, spec := range specs { + for _, selectFn := range selectFns { + if selectFn(spec) { + filtered = append(filtered, spec) + break + } + } + } + + return filtered +} + +// MustSelectAny filters the ExtensionTestSpecs to only those that match any of the provided SelectFunctions. +// if no specs are selected, it will throw an error +func (specs ExtensionTestSpecs) MustSelectAny(selectFns []SelectFunction) (ExtensionTestSpecs, error) { + filtered := specs.SelectAny(selectFns) + if len(filtered) == 0 { + return filtered, fmt.Errorf("no specs selected with specified SelectFunctions") + } + + return filtered, nil +} + +// SelectAll filters the ExtensionTestSpecs to only those that match all the provided SelectFunctions +func (specs ExtensionTestSpecs) SelectAll(selectFns []SelectFunction) ExtensionTestSpecs { + filtered := ExtensionTestSpecs{} + for _, spec := range specs { + anyFalse := false + for _, selectFn := range selectFns { + if !selectFn(spec) { + anyFalse = true + break + } + } + if !anyFalse { + filtered = append(filtered, spec) + } + } + + return filtered +} + +// MustSelectAll filters the ExtensionTestSpecs to only those that match all the provided SelectFunctions. +// if no specs are selected, it will throw an error +func (specs ExtensionTestSpecs) MustSelectAll(selectFns []SelectFunction) (ExtensionTestSpecs, error) { + filtered := specs.SelectAll(selectFns) + if len(filtered) == 0 { + return filtered, fmt.Errorf("no specs selected with specified SelectFunctions") + } + + return filtered, nil +} + +// ModuleTestsOnly ensures that ginkgo tests from vendored sources aren't selected. Unfortunately, making +// use of kubernetes test helpers results in the entire Ginkgo suite being initialized (ginkgo loves global state), +// so we need to be careful about which tests we select. +// +// A test is excluded if ALL of its code locations with full paths are external (vendored or from external test +// suites). If at least one code location with a full path is from the local module, the test is included, because +// local tests may legitimately call helper functions from vendored test frameworks. +func ModuleTestsOnly() SelectFunction { + return func(spec *ExtensionTestSpec) bool { + hasLocalCode := false + + for _, cl := range spec.CodeLocations { + // Short-form code locations (e.g., "set up framework | framework.go:200") are ignored in this determination. + if !strings.Contains(cl, "/") { + continue + } + + // If this code location is not external (vendored or k8s test), it's local code + if !(strings.Contains(cl, "/vendor/") || strings.HasPrefix(cl, "k8s.io/kubernetes")) { + hasLocalCode = true + break + } + } + + // Include the test only if it has at least one local code location + return hasLocalCode + } +} + +// AllTestsIncludingVendored is an alternative to ModuleTestsOnly, which would explicitly opt-in +// to including vendored tests. +func AllTestsIncludingVendored() SelectFunction { + return func(spec *ExtensionTestSpec) bool { + return true + } +} + +// NameContains returns a function that selects specs whose name contains the provided string +func NameContains(name string) SelectFunction { + return func(spec *ExtensionTestSpec) bool { + return strings.Contains(spec.Name, name) + } +} + +// NameContainsAll returns a function that selects specs whose name contains each of the provided contents strings +func NameContainsAll(contents ...string) SelectFunction { + return func(spec *ExtensionTestSpec) bool { + for _, content := range contents { + if !strings.Contains(spec.Name, content) { + return false + } + } + return true + } +} + +// HasLabel returns a function that selects specs with the provided label +func HasLabel(label string) SelectFunction { + return func(spec *ExtensionTestSpec) bool { + return spec.Labels.Has(label) + } +} + +// HasTagWithValue returns a function that selects specs containing a tag with the provided key and value +func HasTagWithValue(key, value string) SelectFunction { + return func(spec *ExtensionTestSpec) bool { + return spec.Tags[key] == value + } +} + +// WithLifecycle returns a function that selects specs with the provided Lifecycle +func WithLifecycle(lifecycle Lifecycle) SelectFunction { + return func(spec *ExtensionTestSpec) bool { + return spec.Lifecycle == lifecycle + } +} + +func (specs ExtensionTestSpecs) Names() []string { + var names []string + for _, spec := range specs { + names = append(names, spec.Name) + } + return names +} + +// Run executes all the specs in parallel, up to maxConcurrent at the same time. Results +// are written to the given ResultWriter after each spec has completed execution. BeforeEach, +// BeforeAll, AfterEach, AfterAll hooks are executed when specified. "Each" hooks must be thread +// safe. Returns an error if any test spec failed, indicating the quantity of failures. +func (specs ExtensionTestSpecs) Run(ctx context.Context, w ResultWriter, maxConcurrent int) ([]*ExtensionTestResult, error) { + queue := make(chan *ExtensionTestSpec) + terminalFailures := atomic.Int64{} + nonTerminalFailures := atomic.Int64{} + + // Execute beforeAll + for _, spec := range specs { + for _, beforeAllTask := range spec.beforeAll { + beforeAllTask.Run() + } + } + + // Feed the queue + go func() { + specs.Walk(func(spec *ExtensionTestSpec) { + queue <- spec + }) + close(queue) + }() + + // if we have only a single spec to run, we do that differently than running multiple. + // multiple specs can run in parallel and do so by exec-ing back into the binary with `run-test` with a single test to execute. + // This means that to avoid infinite recursion, when requesting a single test to run + // we need to run it in process. + runSingleSpec := len(specs) == 1 + + // Start consumers + var wg sync.WaitGroup + resultChan := make(chan *ExtensionTestResult, len(specs)) + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for spec := range queue { + for _, beforeEachTask := range spec.beforeEach { + beforeEachTask.Run(*spec) + } + + res := runSpec(ctx, spec, runSingleSpec) + if res.Result == ResultFailed { + if res.Lifecycle.IsTerminal() { + terminalFailures.Add(1) + } else { + nonTerminalFailures.Add(1) + } + } + + for _, afterEachTask := range spec.afterEach { + afterEachTask.Run(res) + } + + // We can't assume the runner will set the name of a test; it may not know it. Even if + // it does, we may want to modify it (e.g. k8s-tests for annotations currently). + res.Name = spec.Name + w.Write(res) + resultChan <- res + } + }() + } + + // Wait for all consumers to finish + wg.Wait() + close(resultChan) + + // Execute afterAll + for _, spec := range specs { + for _, afterAllTask := range spec.afterAll { + afterAllTask.Run() + } + } + + var results []*ExtensionTestResult + for res := range resultChan { + results = append(results, res) + } + + terminalFailCount := terminalFailures.Load() + nonTerminalFailCount := nonTerminalFailures.Load() + + // Non-terminal failures don't cause exit 1, but we still log them + if nonTerminalFailCount > 0 { + fmt.Fprintf(os.Stderr, "%d informing tests failed (not terminal)\n", nonTerminalFailCount) + } + + // Only exit with error if terminal lifecycle tests failed + if terminalFailCount > 0 { + if nonTerminalFailCount > 0 { + return results, fmt.Errorf("%d tests failed (%d informing)", terminalFailCount+nonTerminalFailCount, nonTerminalFailCount) + } + return results, fmt.Errorf("%d tests failed", terminalFailCount) + } + + return results, nil +} + +// AddBeforeAll adds a function to be run once before all tests start executing. +func (specs ExtensionTestSpecs) AddBeforeAll(fn func()) { + task := &OneTimeTask{fn: fn} + specs.Walk(func(spec *ExtensionTestSpec) { + spec.beforeAll = append(spec.beforeAll, task) + }) +} + +// AddAfterAll adds a function to be run once after all tests have finished. +func (specs ExtensionTestSpecs) AddAfterAll(fn func()) { + task := &OneTimeTask{fn: fn} + specs.Walk(func(spec *ExtensionTestSpec) { + spec.afterAll = append(spec.afterAll, task) + }) +} + +// AddBeforeEach adds a function that runs before each test starts executing. The ExtensionTestSpec is +// passed in for contextual information, but must not be modified. The provided function must be thread +// safe. +func (specs ExtensionTestSpecs) AddBeforeEach(fn func(spec ExtensionTestSpec)) { + task := &SpecTask{fn: fn} + specs.Walk(func(spec *ExtensionTestSpec) { + spec.beforeEach = append(spec.beforeEach, task) + }) +} + +// AddAfterEach adds a function that runs after each test has finished executing. The ExtensionTestResult +// can be modified if needed. The provided function must be thread safe. +func (specs ExtensionTestSpecs) AddAfterEach(fn func(task *ExtensionTestResult)) { + task := &TestResultTask{fn: fn} + specs.Walk(func(spec *ExtensionTestSpec) { + spec.afterEach = append(spec.afterEach, task) + }) +} + +// MustFilter filters specs using the given celExprs. Each celExpr is OR'd together, if any +// match the spec is included in the filtered set. If your CEL expression is invalid or filtering +// otherwise fails, this function panics. +func (specs ExtensionTestSpecs) MustFilter(celExprs []string) ExtensionTestSpecs { + specs, err := specs.Filter(celExprs) + if err != nil { + panic(fmt.Sprintf("filter did not succeed: %s", err.Error())) + } + + return specs +} + +// Filter filters specs using the given celExprs. Each celExpr is OR'd together, if any +// match the spec is included in the filtered set. +func (specs ExtensionTestSpecs) Filter(celExprs []string) (ExtensionTestSpecs, error) { + var filteredSpecs ExtensionTestSpecs + + // Empty filters returns all + if len(celExprs) == 0 { + return specs, nil + } + + env, err := cel.NewEnv( + cel.Declarations( + decls.NewVar("source", decls.String), + decls.NewVar("name", decls.String), + decls.NewVar("originalName", decls.String), + decls.NewVar("labels", decls.NewListType(decls.String)), + decls.NewVar("codeLocations", decls.NewListType(decls.String)), + decls.NewVar("tags", decls.NewMapType(decls.String, decls.String)), + ), + ) + if err != nil { + return nil, fmt.Errorf("failed to create CEL environment: %w", err) + } + + // OR all expressions together + for _, spec := range specs { + include := false + for _, celExpr := range celExprs { + prg, err := programForCEL(env, celExpr) + if err != nil { + return nil, err + } + out, _, err := prg.Eval(map[string]interface{}{ + "name": spec.Name, + "source": spec.Source, + "originalName": spec.OriginalName, + "labels": spec.Labels.UnsortedList(), + "codeLocations": spec.CodeLocations, + "tags": spec.Tags, + }) + if err != nil { + return nil, fmt.Errorf("error evaluating CEL expression: %v", err) + } + + // If any CEL expression evaluates to true, include the TestSpec + if out == types.True { + include = true + break + } + } + if include { + filteredSpecs = append(filteredSpecs, spec) + } + } + + return filteredSpecs, nil +} + +func programForCEL(env *cel.Env, celExpr string) (cel.Program, error) { + // Parse CEL expression + ast, iss := env.Parse(celExpr) + if iss.Err() != nil { + return nil, fmt.Errorf("error parsing CEL expression '%s': %v", celExpr, iss.Err()) + } + + // Check the AST + checked, iss := env.Check(ast) + if iss.Err() != nil { + return nil, fmt.Errorf("error checking CEL expression '%s': %v", celExpr, iss.Err()) + } + + // Create a CEL program from the checked AST + prg, err := env.Program(checked) + if err != nil { + return nil, fmt.Errorf("error creating CEL program: %v", err) + } + return prg, nil +} + +// FilterByEnvironment checks both the Include and Exclude fields of the ExtensionTestSpec to return those specs which match. +// Tests will be included by default unless they are explicitly excluded. If Include is specified, only those tests matching +// the CEL expression will be included. +// +// See helper functions in extensiontests/environment.go to craft CEL expressions +func (specs ExtensionTestSpecs) FilterByEnvironment(envFlags flags.EnvironmentalFlags) (ExtensionTestSpecs, error) { + var filteredSpecs ExtensionTestSpecs + if envFlags.IsEmpty() { + return specs, nil + } + + env, err := cel.NewEnv( + cel.Declarations( + decls.NewVar("apiGroups", decls.NewListType(decls.String)), + decls.NewVar("architecture", decls.String), + decls.NewVar("externalConnectivity", decls.String), + decls.NewVar("fact_keys", decls.NewListType(decls.String)), + decls.NewVar("facts", decls.NewMapType(decls.String, decls.String)), + decls.NewVar("featureGates", decls.NewListType(decls.String)), + decls.NewVar("network", decls.String), + decls.NewVar("networkStack", decls.String), + decls.NewVar("optionalCapabilities", decls.NewListType(decls.String)), + decls.NewVar("platform", decls.String), + decls.NewVar("topology", decls.String), + decls.NewVar("upgrade", decls.String), + decls.NewVar("version", decls.String), + ), + ) + if err != nil { + return nil, fmt.Errorf("failed to create CEL environment: %w", err) + } + factKeys := make([]string, len(envFlags.Facts)) + for k := range envFlags.Facts { + factKeys = append(factKeys, k) + } + vars := map[string]interface{}{ + "apiGroups": envFlags.APIGroups, + "architecture": envFlags.Architecture, + "externalConnectivity": envFlags.ExternalConnectivity, + "fact_keys": factKeys, + "facts": envFlags.Facts, + "featureGates": envFlags.FeatureGates, + "network": envFlags.Network, + "networkStack": envFlags.NetworkStack, + "optionalCapabilities": envFlags.OptionalCapabilities, + "platform": envFlags.Platform, + "topology": envFlags.Topology, + "upgrade": envFlags.Upgrade, + "version": envFlags.Version, + } + + for _, spec := range specs { + envSel := spec.EnvironmentSelector + // If there is no include or exclude CEL, include it implicitly + if envSel.IsEmpty() { + filteredSpecs = append(filteredSpecs, spec) + continue + } + + if envSel.Exclude != "" { + prg, err := programForCEL(env, envSel.Exclude) + if err != nil { + return nil, err + } + out, _, err := prg.Eval(vars) + if err != nil { + return nil, fmt.Errorf("error evaluating CEL expression: %v", err) + } + // If it is explicitly excluded, don't check include + if out == types.True { + continue + } + } + + if envSel.Include != "" { + prg, err := programForCEL(env, envSel.Include) + if err != nil { + return nil, err + } + out, _, err := prg.Eval(vars) + if err != nil { + return nil, fmt.Errorf("error evaluating CEL expression: %v", err) + } + + if out == types.True { + filteredSpecs = append(filteredSpecs, spec) + } + } else { // If it hasn't been excluded, and there is no "include" it will be implicitly included + filteredSpecs = append(filteredSpecs, spec) + } + + } + + return filteredSpecs, nil +} + +// AddLabel adds the labels to each spec. +func (specs ExtensionTestSpecs) AddLabel(labels ...string) ExtensionTestSpecs { + for i := range specs { + specs[i].Labels.Insert(labels...) + } + + return specs +} + +// RemoveLabel removes the labels from each spec. +func (specs ExtensionTestSpecs) RemoveLabel(labels ...string) ExtensionTestSpecs { + for i := range specs { + specs[i].Labels.Delete(labels...) + } + + return specs +} + +// SetTag specifies a key/value pair for each spec. +func (specs ExtensionTestSpecs) SetTag(key, value string) ExtensionTestSpecs { + for i := range specs { + specs[i].Tags[key] = value + } + + return specs +} + +// UnsetTag removes the specified key from each spec. +func (specs ExtensionTestSpecs) UnsetTag(key string) ExtensionTestSpecs { + for i := range specs { + delete(specs[i].Tags, key) + } + + return specs +} + +// Include adds the specified CEL expression to explicitly include tests by environment to each spec +func (specs ExtensionTestSpecs) Include(includeCEL string) ExtensionTestSpecs { + for _, spec := range specs { + spec.Include(includeCEL) + } + return specs +} + +// Exclude adds the specified CEL expression to explicitly exclude tests by environment to each spec +func (specs ExtensionTestSpecs) Exclude(excludeCEL string) ExtensionTestSpecs { + for _, spec := range specs { + spec.Exclude(excludeCEL) + } + return specs +} + +// Include adds the specified CEL expression to explicitly include tests by environment. +// If there is already an "include" defined, it will OR the expressions together +func (spec *ExtensionTestSpec) Include(includeCEL string) *ExtensionTestSpec { + existingInclude := spec.EnvironmentSelector.Include + if existingInclude != "" { + includeCEL = fmt.Sprintf("(%s) || (%s)", existingInclude, includeCEL) + } + + spec.EnvironmentSelector.Include = includeCEL + return spec +} + +// Exclude adds the specified CEL expression to explicitly exclude tests by environment. +// If there is already an "exclude" defined, it will OR the expressions together +func (spec *ExtensionTestSpec) Exclude(excludeCEL string) *ExtensionTestSpec { + existingExclude := spec.EnvironmentSelector.Exclude + if existingExclude != "" { + excludeCEL = fmt.Sprintf("(%s) || (%s)", existingExclude, excludeCEL) + } + + spec.EnvironmentSelector.Exclude = excludeCEL + return spec +} + +func runSpec(ctx context.Context, spec *ExtensionTestSpec, runSingleSpec bool) *ExtensionTestResult { + startTime := time.Now().UTC() + var res *ExtensionTestResult + if runSingleSpec || spec.RunParallel == nil { + res = spec.Run(ctx) + } else { + res = spec.RunParallel(ctx) + } + duration := time.Since(startTime) + endTime := startTime.Add(duration).UTC() + if res == nil { + // this shouldn't happen + panic(fmt.Sprintf("test produced no result: %s", spec.Name)) + } + + res.Lifecycle = spec.Lifecycle + + // If the runner doesn't populate this info, we should set it + if res.StartTime == nil { + res.StartTime = dbtime.Ptr(startTime) + } + if res.EndTime == nil { + res.EndTime = dbtime.Ptr(endTime) + } + if res.Duration == 0 { + res.Duration = duration.Milliseconds() + } + + return res +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/task.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/task.go new file mode 100644 index 000000000..e808bea87 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/task.go @@ -0,0 +1,31 @@ +package extensiontests + +import "sync/atomic" + +type SpecTask struct { + fn func(spec ExtensionTestSpec) +} + +func (t *SpecTask) Run(spec ExtensionTestSpec) { + t.fn(spec) +} + +type TestResultTask struct { + fn func(result *ExtensionTestResult) +} + +func (t *TestResultTask) Run(result *ExtensionTestResult) { + t.fn(result) +} + +type OneTimeTask struct { + fn func() + executed int32 // Atomic boolean to indicate whether the function has been run +} + +func (t *OneTimeTask) Run() { + // Ensure one-time tasks are only run once + if atomic.CompareAndSwapInt32(&t.executed, 0, 1) { + t.fn() + } +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/types.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/types.go new file mode 100644 index 000000000..f3edf41a6 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/types.go @@ -0,0 +1,124 @@ +package extensiontests + +import ( + "context" + "time" + + "github.com/openshift-eng/openshift-tests-extension/pkg/dbtime" + "github.com/openshift-eng/openshift-tests-extension/pkg/util/sets" +) + +type Lifecycle string + +var LifecycleInforming Lifecycle = "informing" +var LifecycleBlocking Lifecycle = "blocking" + +// IsTerminal returns true if failures in tests with this lifecycle should cause +// the test run to exit with a non-zero exit code. +func (l Lifecycle) IsTerminal() bool { + return l != LifecycleInforming +} + +type ExtensionTestSpecs []*ExtensionTestSpec + +type ExtensionTestSpec struct { + Name string `json:"name"` + + // OriginalName contains the very first name this test was ever known as, used to preserve + // history across all names. + OriginalName string `json:"originalName,omitempty"` + + // Labels are single string values to apply to the test spec + Labels sets.Set[string] `json:"labels"` + + // Tags are key:value pairs + Tags map[string]string `json:"tags,omitempty"` + + // Resources gives optional information about what's required to run this test. + Resources Resources `json:"resources"` + + // Source is the origin of the test. + Source string `json:"source"` + + // CodeLocations are the files where the spec originates from. + CodeLocations []string `json:"codeLocations,omitempty"` + + // Lifecycle informs the executor whether the test is informing only, and should not cause the + // overall job run to fail, or if it's blocking where a failure of the test is fatal. + // Informing lifecycle tests can be used temporarily to gather information about a test's stability. + // Tests must not remain informing forever. + Lifecycle Lifecycle `json:"lifecycle"` + + // EnvironmentSelector allows for CEL expressions to be used to control test inclusion + EnvironmentSelector EnvironmentSelector `json:"environmentSelector,omitempty"` + + // Run invokes a test in-process. It must not call back into `ote-binary run-test` because that will usually + // cause an infinite recursion. + Run func(ctx context.Context) *ExtensionTestResult `json:"-"` + + // RunParallel invokes a test in parallel with other tests. This is usually done by exec-ing out + // to the `ote-binary run-test "test name"` commmand and interpretting the result. + RunParallel func(ctx context.Context) *ExtensionTestResult `json:"-"` + + // Timeout is the maximum duration for this test. If set, it overrides the default 90-minute + // timeout used by SpawnProcessToRunTest. This is typically populated from Suite.TestTimeout. + Timeout time.Duration `json:"-"` + + // Hook functions + afterAll []*OneTimeTask + beforeAll []*OneTimeTask + afterEach []*TestResultTask + beforeEach []*SpecTask +} + +type Resources struct { + Isolation Isolation `json:"isolation"` + Memory string `json:"memory,omitempty"` + Duration string `json:"duration,omitempty"` + Timeout string `json:"timeout,omitempty"` +} + +type Isolation struct { + Mode string `json:"mode,omitempty"` + Conflict []string `json:"conflict,omitempty"` + Taint []string `json:"taint,omitempty"` + Toleration []string `json:"toleration,omitempty"` +} + +type EnvironmentSelector struct { + Include string `json:"include,omitempty"` + Exclude string `json:"exclude,omitempty"` +} + +func (e EnvironmentSelector) IsEmpty() bool { + return e.Include == "" && e.Exclude == "" +} + +type ExtensionTestResults []*ExtensionTestResult + +type Result string + +var ResultPassed Result = "passed" +var ResultSkipped Result = "skipped" +var ResultFailed Result = "failed" + +type ExtensionTestResult struct { + Name string `json:"name"` + Lifecycle Lifecycle `json:"lifecycle"` + Duration int64 `json:"duration"` + StartTime *dbtime.DBTime `json:"startTime"` + EndTime *dbtime.DBTime `json:"endTime"` + Result Result `json:"result"` + Output string `json:"output"` + Error string `json:"error,omitempty"` + Details []Details `json:"details,omitempty"` +} + +// Details are human-readable messages to further explain skips, timeouts, etc. +// It can also be used to provide contemporaneous information about failures +// that may not be easily returned by must-gather. For larger artifacts (greater than +// 10KB, write them to $EXTENSION_ARTIFACTS_DIR. +type Details struct { + Name string `json:"name"` + Value interface{} `json:"value"` +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/viewer.html b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/viewer.html new file mode 100644 index 000000000..2ff236aa3 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/viewer.html @@ -0,0 +1,1520 @@ + + + + + + Results for {{ .SuiteName }} + + + +
+
+

+ + + + + Results for {{ .SuiteName }} +

+

No file loaded

+
+ +
+

Load Test Results

+

Drag and drop a JSON test results file here, or click to browse

+ +
+ + + + +
+ + + + + diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/registry.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/registry.go new file mode 100644 index 000000000..bbae421df --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/registry.go @@ -0,0 +1,39 @@ +package extension + +const DefaultExtension = "default" + +type Registry struct { + extensions map[string]*Extension +} + +func NewRegistry() *Registry { + var r Registry + return &r +} + +func (r *Registry) Walk(walkFn func(*Extension)) { + for k := range r.extensions { + if k == DefaultExtension { + continue + } + walkFn(r.extensions[k]) + } +} + +func (r *Registry) Get(name string) *Extension { + return r.extensions[name] +} + +func (r *Registry) Register(extension *Extension) { + if r.extensions == nil { + r.extensions = make(map[string]*Extension) + // first extension is default + r.extensions[DefaultExtension] = extension + } + + r.extensions[extension.Component.Identifier()] = extension +} + +func (r *Registry) Deregister(name string) { + delete(r.extensions, name) +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/types.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/types.go new file mode 100644 index 000000000..00d2d9d66 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/types.go @@ -0,0 +1,94 @@ +package extension + +import ( + "time" + + "github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests" + "github.com/openshift-eng/openshift-tests-extension/pkg/util/sets" +) + +const CurrentExtensionAPIVersion = "v1.1" + +// Extension represents an extension to openshift-tests. +type Extension struct { + APIVersion string `json:"apiVersion"` + Source Source `json:"source"` + Component Component `json:"component"` + + // Suites that the extension wants to advertise/participate in. + Suites []Suite `json:"suites"` + + Images []Image `json:"images"` + + // Private data + specs extensiontests.ExtensionTestSpecs + obsoleteTests sets.Set[string] +} + +// Source contains the details of the commit and source URL. +type Source struct { + // Commit from which this binary was compiled. + Commit string `json:"commit"` + // BuildDate ISO8601 string of when the binary was built + BuildDate string `json:"build_date"` + // GitTreeState lets you know the status of the git tree (clean/dirty) + GitTreeState string `json:"git_tree_state"` + // SourceURL contains the url of the git repository (if known) that this extension was built from. + SourceURL string `json:"source_url,omitempty"` +} + +// Component represents the component the binary acts on. +type Component struct { + // The product this component is part of. + Product string `json:"product"` + // The type of the component. + Kind string `json:"type"` + // The name of the component. + Name string `json:"name"` +} + +type ClusterStability string + +var ( + // ClusterStabilityStable means that at no point during testing do we expect a component to take downtime and upgrades are not happening. + ClusterStabilityStable ClusterStability = "Stable" + + // ClusterStabilityDisruptive means that the suite is expected to induce outages to the cluster. + ClusterStabilityDisruptive ClusterStability = "Disruptive" + + // ClusterStabilityUpgrade was previously defined, but was removed by @deads2k. Please contact him if you find a use + // case for it and needs to be reintroduced. + // ClusterStabilityUpgrade ClusterStability = "Upgrade" +) + +// Suite represents additional suites the extension wants to advertise. Child suites when being executed in the context +// of a parent will have their count, parallelism, stability, and timeout options superseded by the parent's suite. +type Suite struct { + Name string `json:"name"` + Description string `json:"description"` + + // Parents are the parent suites this suite is part of. + Parents []string `json:"parents,omitempty"` + // Qualifiers are CEL expressions that are OR'd together for test selection that are members of the suite. + Qualifiers []string `json:"qualifiers,omitempty"` + + // Count is the default number of times to execute each test in this suite. + Count int `json:"count,omitempty"` + // Parallelism is the maximum parallelism of this suite. + Parallelism int `json:"parallelism,omitempty"` + // ClusterStability informs openshift-tests whether this entire test suite is expected to be disruptive or not + // to normal cluster operations. + ClusterStability ClusterStability `json:"clusterStability,omitempty"` + // TestTimeout is the default timeout for tests in this suite. + TestTimeout *time.Duration `json:"testTimeout,omitempty"` +} + +type Image struct { + Index int `json:"index"` + Registry string `json:"registry"` + Name string `json:"name"` + Version string `json:"version"` + // Mapped is the image reference that this image is mirrored to by the image mirror tool. + // This field should be populated if the mirrored image reference is predetermined by the test extensions. + Mapped *Image `json:"mapped,omitempty"` +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/component.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/component.go new file mode 100644 index 000000000..ca9e425c4 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/component.go @@ -0,0 +1,25 @@ +package flags + +import ( + "github.com/spf13/pflag" +) + +const DefaultExtension = "default" + +// ComponentFlags contains information for specifying the component. +type ComponentFlags struct { + Component string +} + +func NewComponentFlags() *ComponentFlags { + return &ComponentFlags{ + Component: DefaultExtension, + } +} + +func (f *ComponentFlags) BindFlags(fs *pflag.FlagSet) { + fs.StringVar(&f.Component, + "component", + f.Component, + "specify the component to enable") +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/concurrency.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/concurrency.go new file mode 100644 index 000000000..2db07c765 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/concurrency.go @@ -0,0 +1,23 @@ +package flags + +import "github.com/spf13/pflag" + +// ConcurrencyFlags contains information for configuring concurrency +type ConcurrencyFlags struct { + MaxConcurency int +} + +func NewConcurrencyFlags() *ConcurrencyFlags { + return &ConcurrencyFlags{ + MaxConcurency: 10, + } +} + +func (f *ConcurrencyFlags) BindFlags(fs *pflag.FlagSet) { + fs.IntVarP(&f.MaxConcurency, + "max-concurrency", + "c", + f.MaxConcurency, + "maximum number of tests to run in parallel", + ) +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/environment.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/environment.go new file mode 100644 index 000000000..af7a0258e --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/environment.go @@ -0,0 +1,114 @@ +package flags + +import ( + "reflect" + + "github.com/spf13/pflag" +) + +type EnvironmentalFlags struct { + APIGroups []string + Architecture string + ExternalConnectivity string + Facts map[string]string + FeatureGates []string + Network string + NetworkStack string + OptionalCapabilities []string + Platform string + Topology string + Upgrade string + Version string +} + +func NewEnvironmentalFlags() *EnvironmentalFlags { + return &EnvironmentalFlags{} +} + +func (f *EnvironmentalFlags) BindFlags(fs *pflag.FlagSet) { + fs.StringArrayVar(&f.APIGroups, + "api-group", + f.APIGroups, + "The API groups supported by this cluster. Since: v1.1") + fs.StringVar(&f.Architecture, + "architecture", + "", + "The CPU architecture of the target cluster (\"amd64\", \"arm64\"). Since: v1.0") + fs.StringVar(&f.ExternalConnectivity, + "external-connectivity", + "", + "The External Connectivity of the target cluster (\"Disconnected\", \"Direct\", \"Proxied\"). Since: v1.0") + fs.StringArrayVar(&f.FeatureGates, + "feature-gate", + f.FeatureGates, + "The feature gates enabled on this cluster. Since: v1.1") + fs.StringToStringVar(&f.Facts, + "fact", + make(map[string]string), + "Facts advertised by cluster components. Since: v1.0") + fs.StringVar(&f.Network, + "network", + "", + "The network of the target cluster (\"ovn\", \"sdn\"). Since: v1.0") + fs.StringVar(&f.NetworkStack, + "network-stack", + "", + "The network stack of the target cluster (\"ipv6\", \"ipv4\", \"dual\"). Since: v1.0") + fs.StringSliceVar(&f.OptionalCapabilities, + "optional-capability", + []string{}, + "An Optional Capability of the target cluster. Can be passed multiple times. Since: v1.0") + fs.StringVar(&f.Platform, + "platform", + "", + "The hardware or cloud platform (\"aws\", \"gcp\", \"metal\", ...). Since: v1.0") + fs.StringVar(&f.Topology, + "topology", + "", + "The target cluster topology (\"ha\", \"microshift\", ...). Since: v1.0") + fs.StringVar(&f.Upgrade, + "upgrade", + "", + "The upgrade that was performed prior to the test run (\"micro\", \"minor\"). Since: v1.0") + fs.StringVar(&f.Version, + "version", + "", + "\"major.minor\" version of target cluster. Since: v1.0") +} + +func (f *EnvironmentalFlags) IsEmpty() bool { + v := reflect.ValueOf(*f) + + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + + switch field.Kind() { + case reflect.Slice, reflect.Map: + if !field.IsNil() && field.Len() > 0 { + return false + } + default: + if !reflect.DeepEqual(field.Interface(), reflect.Zero(field.Type()).Interface()) { + return false + } + } + } + + return true +} + +// EnvironmentFlagVersions holds the "Since" version metadata for each flag. +var EnvironmentFlagVersions = map[string]string{ + "api-group": "v1.1", + "architecture": "v1.0", + "external-connectivity": "v1.0", + "fact": "v1.0", + "feature-gate": "v1.1", + "network": "v1.0", + "network-stack": "v1.0", + "optional-capability": "v1.0", + "platform": "v1.0", + "topology": "v1.0", + "upgrade": "v1.0", + "version": "v1.0", +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/names.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/names.go new file mode 100644 index 000000000..9e5864839 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/names.go @@ -0,0 +1,24 @@ +package flags + +import ( + "github.com/spf13/pflag" +) + +// NamesFlags contains information for specifying multiple test names. +type NamesFlags struct { + Names []string +} + +func NewNamesFlags() *NamesFlags { + return &NamesFlags{ + Names: []string{}, + } +} + +func (f *NamesFlags) BindFlags(fs *pflag.FlagSet) { + fs.StringArrayVarP(&f.Names, + "names", + "n", + f.Names, + "specify test name (can be specified multiple times)") +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/output.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/output.go new file mode 100644 index 000000000..af62bbf13 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/output.go @@ -0,0 +1,96 @@ +package flags + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "strings" + + "github.com/spf13/pflag" +) + +// OutputFlags contains information for specifying multiple test names. +type OutputFlags struct { + Output string +} + +func NewOutputFlags() *OutputFlags { + return &OutputFlags{ + Output: "json", + } +} + +func (f *OutputFlags) BindFlags(fs *pflag.FlagSet) { + fs.StringVarP(&f.Output, + "output", + "o", + f.Output, + "output mode") +} + +func (o *OutputFlags) Marshal(v interface{}) ([]byte, error) { + switch o.Output { + case "", "json": + j, err := json.MarshalIndent(&v, "", " ") + if err != nil { + return nil, err + } + return j, nil + case "jsonl": + // Check if v is a slice or array + val := reflect.ValueOf(v) + if val.Kind() == reflect.Slice || val.Kind() == reflect.Array { + var result []byte + for i := 0; i < val.Len(); i++ { + item := val.Index(i).Interface() + j, err := json.Marshal(item) + if err != nil { + return nil, err + } + result = append(result, j...) + result = append(result, '\n') // Append newline after each item + } + return result, nil + } + return nil, errors.New("jsonl format requires a slice or array") + case "names": + val := reflect.ValueOf(v) + if val.Kind() == reflect.Slice || val.Kind() == reflect.Array { + var names []string + outerLoop: + for i := 0; i < val.Len(); i++ { + item := val.Index(i) + // Check for Name() or Identifier() methods + itemInterface := item.Interface() + nameFuncs := []string{"Name", "Identifier"} + for _, fn := range nameFuncs { + method := reflect.ValueOf(itemInterface).MethodByName(fn) + if method.IsValid() && method.Kind() == reflect.Func && method.Type().NumIn() == 0 && method.Type().NumOut() == 1 && method.Type().Out(0).Kind() == reflect.String { + name := method.Call(nil)[0].String() + names = append(names, name) + continue outerLoop + } + } + + // Dereference pointer if needed + if item.Kind() == reflect.Ptr { + item = item.Elem() + } + // Check for struct with Name field + if item.Kind() == reflect.Struct { + nameField := item.FieldByName("Name") + if nameField.IsValid() && nameField.Kind() == reflect.String { + names = append(names, nameField.String()) + } + } else { + return nil, errors.New("items must have a Name field or a Name() method") + } + } + return []byte(strings.Join(names, "\n")), nil + } + return nil, errors.New("names format requires an array of structs") + default: + return nil, fmt.Errorf("invalid output format: %s", o.Output) + } +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/suite.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/suite.go new file mode 100644 index 000000000..23de832a8 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/suite.go @@ -0,0 +1,21 @@ +package flags + +import ( + "github.com/spf13/pflag" +) + +// SuiteFlags contains information for specifying the suite. +type SuiteFlags struct { + Suite string +} + +func NewSuiteFlags() *SuiteFlags { + return &SuiteFlags{} +} + +func (f *SuiteFlags) BindFlags(fs *pflag.FlagSet) { + fs.StringVar(&f.Suite, + "suite", + f.Suite, + "specify the suite to use") +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/logging.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/logging.go new file mode 100644 index 000000000..1cf299a7c --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/logging.go @@ -0,0 +1,25 @@ +package ginkgo + +import ( + "github.com/go-logr/logr" + "github.com/go-logr/logr/funcr" + "github.com/onsi/ginkgo/v2" +) + +// this is copied from ginkgo because ginkgo made it internal and then hardcoded an init block +// using these functions to wire to os.stdout and we want to wire to stderr (or a different buffer) so we can +// have json output. + +func GinkgoLogrFunc(writer ginkgo.GinkgoWriterInterface) logr.Logger { + return funcr.New(func(prefix, args string) { + if prefix == "" { + writer.Printf("%s\n", args) + } else { + writer.Printf("%s %s\n", prefix, args) + } + }, funcr.Options{ + // LogTimestamp adds timestamps to log lines using the format "2006-01-02 15:04:05.000000" + // See: https://github.com/go-logr/logr/blob/bb8ea8159175ccb4eddf4ac8704f84e40ac6d9b0/funcr/funcr.go#L211 + LogTimestamp: true, + }) +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/parallel.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/parallel.go new file mode 100644 index 000000000..890cebb09 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/parallel.go @@ -0,0 +1,139 @@ +package ginkgo + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "os" + "os/exec" + "syscall" + "time" + + "github.com/openshift-eng/openshift-tests-extension/pkg/dbtime" + "github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests" +) + +func SpawnProcessToRunTest(ctx context.Context, testName string, timeout time.Duration) *extensiontests.ExtensionTestResult { + // longerCtx is used to backstop the process, but leave termination up to us if possible to allow a double interrupt + longerCtx, longerCancel := context.WithTimeout(ctx, timeout+15*time.Minute) + defer longerCancel() + timeoutCtx, shorterCancel := context.WithTimeout(longerCtx, timeout) + defer shorterCancel() + + stdout := &bytes.Buffer{} + stderr := &bytes.Buffer{} + + command := exec.CommandContext(longerCtx, os.Args[0], "run-test", "--output=json", testName) + command.Stdout = stdout + command.Stderr = stderr + + start := time.Now() + err := command.Start() + if err != nil { + fmt.Fprintf(stderr, "Command Start Error: %v\n", err) + return newTestResult(testName, extensiontests.ResultFailed, start, time.Now(), stdout, stderr) + } + + go func() { + select { + // interrupt tests after timeout, and abort if they don't complete quick enough + case <-time.After(timeout): + if command.Process != nil { + // we're not going to do anything with the err + _ = command.Process.Signal(syscall.SIGINT) + } + // if the process appears to be hung a significant amount of time after the timeout + // send an ABRT so we get a stack dump + select { + case <-time.After(time.Minute): + if command.Process != nil { + // we're not going to do anything with the err + _ = command.Process.Signal(syscall.SIGABRT) + } + case <-timeoutCtx.Done(): + if command.Process != nil { + _ = command.Process.Signal(syscall.SIGABRT) + } + } + case <-timeoutCtx.Done(): + if command.Process != nil { + _ = command.Process.Signal(syscall.SIGINT) + } + } + }() + + result := extensiontests.ResultFailed + cmdErr := command.Wait() + + subcommandResult, parseErr := newTestResultFromOutput(stdout) + if parseErr == nil { + // even if we have a cmdErr, if we were able to parse the result, trust the output + return subcommandResult + } + + fmt.Fprintf(stderr, "Command Error: %v\n", cmdErr) + fmt.Fprintf(stderr, "Deserializaion Error: %v\n", parseErr) + return newTestResult(testName, result, start, time.Now(), stdout, stderr) +} + +func newTestResultFromOutput(stdout *bytes.Buffer) (*extensiontests.ExtensionTestResult, error) { + if len(stdout.Bytes()) == 0 { + return nil, errors.New("no output from command") + } + + // when the command runs correctly, we get json or json slice output + retArray := []extensiontests.ExtensionTestResult{} + if arrayItemErr := json.Unmarshal(stdout.Bytes(), &retArray); arrayItemErr == nil { + if len(retArray) != 1 { + return nil, errors.New("expected 1 result, got %v results") + } + return &retArray[0], nil + } + + // when the command runs correctly, we get json output + ret := &extensiontests.ExtensionTestResult{} + if singleItemErr := json.Unmarshal(stdout.Bytes(), ret); singleItemErr != nil { + return nil, singleItemErr + } + + return ret, nil +} + +func newTestResult(name string, result extensiontests.Result, start, end time.Time, stdout, stderr *bytes.Buffer) *extensiontests.ExtensionTestResult { + duration := end.Sub(start) + dbStart := dbtime.DBTime(start) + dbEnd := dbtime.DBTime(start) + ret := &extensiontests.ExtensionTestResult{ + Name: name, + Lifecycle: "", // lifecycle is completed one level above this. + Duration: int64(duration), + StartTime: &dbStart, + EndTime: &dbEnd, + Result: result, + Details: nil, + } + + if stdout != nil && stderr != nil { + stdoutStr := stdout.String() + stderrStr := stderr.String() + + ret.Output = fmt.Sprintf("STDOUT:\n%s\n\nSTDERR:\n%s\n", stdoutStr, stderrStr) + + // try to choose the best summary + switch { + case len(stderrStr) > 0 && len(stderrStr) < 5000: + ret.Error = stderrStr + case len(stderrStr) > 0 && len(stderrStr) >= 5000: + ret.Error = stderrStr[len(stderrStr)-5000:] + + case len(stdoutStr) > 0 && len(stdoutStr) < 5000: + ret.Error = stdoutStr + case len(stdoutStr) > 0 && len(stdoutStr) >= 5000: + ret.Error = stdoutStr[len(stdoutStr)-5000:] + } + } + + return ret +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/util.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/util.go new file mode 100644 index 000000000..d2b0ee9d0 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/util.go @@ -0,0 +1,231 @@ +package ginkgo + +import ( + "context" + "fmt" + "os" + "strings" + "sync" + "time" + + "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2/types" + "github.com/onsi/gomega" + + "github.com/openshift-eng/openshift-tests-extension/pkg/util/sets" + + ext "github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests" +) + +func configureGinkgo() (*types.SuiteConfig, *types.ReporterConfig, error) { + if !ginkgo.GetSuite().InPhaseBuildTree() { + if err := ginkgo.GetSuite().BuildTree(); err != nil { + return nil, nil, fmt.Errorf("couldn't build ginkgo tree: %w", err) + } + } + + // Ginkgo initialization + ginkgo.GetSuite().ClearBeforeAndAfterSuiteNodes() + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + suiteConfig.RandomizeAllSpecs = true + suiteConfig.Timeout = 24 * time.Hour + reporterConfig.NoColor = true + reporterConfig.Verbose = true + ginkgo.SetReporterConfig(reporterConfig) + + // Write output to Stderr + ginkgo.GinkgoWriter = ginkgo.NewWriter(os.Stderr) + ginkgo.GinkgoLogr = GinkgoLogrFunc(ginkgo.GinkgoWriter) + + gomega.RegisterFailHandler(ginkgo.Fail) + + return &suiteConfig, &reporterConfig, nil +} + +// BuildExtensionTestSpecsFromOpenShiftGinkgoSuite generates OTE specs for Gingko tests. While OTE isn't limited to +// calling ginkgo tests, anything that implements the ExtensionTestSpec interface can be used, it's the most common +// course of action. The typical use case is to omit selectFns, but if provided, these will filter the returned list +// of specs, applied in the order provided. +func BuildExtensionTestSpecsFromOpenShiftGinkgoSuite(selectFns ...ext.SelectFunction) (ext.ExtensionTestSpecs, error) { + var specs ext.ExtensionTestSpecs + var enforceSerialExecutionForGinkgo sync.Mutex // in-process parallelization for ginkgo is impossible so far + + if _, _, err := configureGinkgo(); err != nil { + return nil, err + } + + cwd, err := os.Getwd() + if err != nil { + return nil, fmt.Errorf("couldn't get current working directory: %w", err) + } + + ginkgo.GetSuite().WalkTests(func(name string, spec types.TestSpec) { + var codeLocations []string + for _, cl := range spec.CodeLocations() { + codeLocations = append(codeLocations, cl.String()) + } + + testCase := &ext.ExtensionTestSpec{ + Name: spec.Text(), + Labels: sets.New[string](spec.Labels()...), + CodeLocations: codeLocations, + Lifecycle: GetLifecycle(spec.Labels()), + Run: func(ctx context.Context) *ext.ExtensionTestResult { + enforceSerialExecutionForGinkgo.Lock() + defer enforceSerialExecutionForGinkgo.Unlock() + + suiteConfig, reporterConfig, _ := configureGinkgo() + + result := &ext.ExtensionTestResult{ + Name: spec.Text(), + } + + var summary types.SpecReport + ginkgo.GetSuite().RunSpec(spec, ginkgo.Labels{}, "", cwd, ginkgo.GetFailer(), ginkgo.GetWriter(), *suiteConfig, + *reporterConfig) + for _, report := range ginkgo.GetSuite().GetReport().SpecReports { + if report.NumAttempts > 0 { + summary = report + } + } + + result.Output = summary.CapturedGinkgoWriterOutput + result.Error = summary.CapturedStdOutErr + + switch { + case summary.State == types.SpecStatePassed: + result.Result = ext.ResultPassed + case summary.State == types.SpecStateSkipped, summary.State == types.SpecStatePending: + result.Result = ext.ResultSkipped + if len(summary.Failure.Message) > 0 { + result.Output = fmt.Sprintf( + "%s\n skip [%s:%d]: %s\n", + result.Output, + lastFilenameSegment(summary.Failure.Location.FileName), + summary.Failure.Location.LineNumber, + summary.Failure.Message, + ) + } else if len(summary.Failure.ForwardedPanic) > 0 { + result.Output = fmt.Sprintf( + "%s\n skip [%s:%d]: %s\n", + result.Output, + lastFilenameSegment(summary.Failure.Location.FileName), + summary.Failure.Location.LineNumber, + summary.Failure.ForwardedPanic, + ) + } + case summary.State == types.SpecStateFailed, summary.State == types.SpecStatePanicked, summary.State == types.SpecStateInterrupted, summary.State == types.SpecStateAborted: + result.Result = ext.ResultFailed + var errors []string + if len(summary.Failure.ForwardedPanic) > 0 { + if len(summary.Failure.Location.FullStackTrace) > 0 { + errors = append(errors, fmt.Sprintf("\n%s\n", summary.Failure.Location.FullStackTrace)) + } + errors = append(errors, fmt.Sprintf("fail [%s:%d]: Test Panicked: %s", lastFilenameSegment(summary.Failure.Location.FileName), summary.Failure.Location.LineNumber, summary.Failure.ForwardedPanic)) + } + errors = append(errors, fmt.Sprintf("fail [%s:%d]: %s", lastFilenameSegment(summary.Failure.Location.FileName), summary.Failure.Location.LineNumber, summary.Failure.Message)) + result.Error = strings.Join(errors, "\n") + case summary.State == types.SpecStateTimedout: + result.Result = ext.ResultFailed + var errors []string + for _, additionalFailure := range summary.AdditionalFailures { + collectAdditionalFailures(&errors, " ", additionalFailure.Failure) + } + if summary.Failure.AdditionalFailure != nil { + collectAdditionalFailures(&errors, " ", summary.Failure.AdditionalFailure.Failure) + } + errors = append(errors, fmt.Sprintf("fail [%s:%d]: %s", lastFilenameSegment(summary.Failure.Location.FileName), summary.Failure.Location.LineNumber, summary.Failure.Message)) + result.Error = strings.Join(errors, "\n") + default: + panic(fmt.Sprintf("test produced unknown outcome: %#v", summary)) + } + + return result + }, + } + testCase.RunParallel = func(ctx context.Context) *ext.ExtensionTestResult { + timeout := 90 * time.Minute + if testCase.Timeout > 0 { + timeout = testCase.Timeout + } + return SpawnProcessToRunTest(ctx, name, timeout) + } + specs = append(specs, testCase) + }) + + // Default select function is to exclude vendored specs. When relying on Kubernetes test framework for its helpers, + // it also unfortunately ends up importing *all* Gingko specs. This is unsafe: it would potentially override the + // kube specs already present in origin. The best course of action is enforce this behavior on everyone. If for + // some reason, you must include vendored specs, you can opt-in directly by supplying your own SelectFunctions or using + // AllTestsIncludedVendored(). + if len(selectFns) == 0 { + selectFns = []ext.SelectFunction{ext.ModuleTestsOnly()} + } + + for _, selectFn := range selectFns { + specs = specs.Select(selectFn) + } + + return specs, nil +} + +func Informing() ginkgo.Labels { + return ginkgo.Label(fmt.Sprintf("Lifecycle:%s", ext.LifecycleInforming)) +} + +func Slow() ginkgo.Labels { + return ginkgo.Label("SLOW") +} + +func Blocking() ginkgo.Labels { + return ginkgo.Label(fmt.Sprintf("Lifecycle:%s", ext.LifecycleBlocking)) +} + +func GetLifecycle(labels ginkgo.Labels) ext.Lifecycle { + for _, label := range labels { + res := strings.Split(label, ":") + if len(res) != 2 || !strings.EqualFold(res[0], "lifecycle") { + continue + } + return MustLifecycle(res[1]) // this panics if unsupported lifecycle is used + } + + return ext.LifecycleBlocking +} + +func MustLifecycle(l string) ext.Lifecycle { + switch ext.Lifecycle(l) { + case ext.LifecycleInforming, ext.LifecycleBlocking: + return ext.Lifecycle(l) + default: + panic(fmt.Sprintf("unknown test lifecycle: %s", l)) + } +} + +func lastFilenameSegment(filename string) string { + if parts := strings.Split(filename, "/vendor/"); len(parts) > 1 { + return parts[len(parts)-1] + } + if parts := strings.Split(filename, "/src/"); len(parts) > 1 { + return parts[len(parts)-1] + } + return filename +} + +func collectAdditionalFailures(errors *[]string, suffix string, failure types.Failure) { + if failure.IsZero() { + return + } + + if len(failure.ForwardedPanic) > 0 { + if len(failure.Location.FullStackTrace) > 0 { + *errors = append(*errors, fmt.Sprintf("\n%s\n", failure.Location.FullStackTrace)) + } + *errors = append(*errors, fmt.Sprintf("fail [%s:%d]: Test Panicked: %s%s", lastFilenameSegment(failure.Location.FileName), failure.Location.LineNumber, failure.ForwardedPanic, suffix)) + } + *errors = append(*errors, fmt.Sprintf("fail [%s:%d] %s%s", lastFilenameSegment(failure.Location.FileName), failure.Location.LineNumber, failure.Message, suffix)) + + if failure.AdditionalFailure != nil { + collectAdditionalFailures(errors, " ", failure.AdditionalFailure.Failure) + } +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/junit/types.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/junit/types.go new file mode 100644 index 000000000..0309fbd51 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/junit/types.go @@ -0,0 +1,104 @@ +package junit + +import ( + "encoding/xml" +) + +// The below types are directly marshalled into XML. The types correspond to jUnit +// XML schema, but do not contain all valid fields. For instance, the class name +// field for test cases is omitted, as this concept does not directly apply to Go. +// For XML specifications see http://help.catchsoftware.com/display/ET/JUnit+Format +// or view the XSD included in this package as 'junit.xsd' + +// TestSuites represents a flat collection of jUnit test suites. +type TestSuites struct { + XMLName xml.Name `xml:"testsuites"` + + // Suites are the jUnit test suites held in this collection + Suites []*TestSuite `xml:"testsuite"` +} + +// TestSuite represents a single jUnit test suite, potentially holding child suites. +type TestSuite struct { + XMLName xml.Name `xml:"testsuite"` + + // Name is the name of the test suite + Name string `xml:"name,attr"` + + // NumTests records the number of tests in the TestSuite + NumTests uint `xml:"tests,attr"` + + // NumSkipped records the number of skipped tests in the suite + NumSkipped uint `xml:"skipped,attr"` + + // NumFailed records the number of failed tests in the suite + NumFailed uint `xml:"failures,attr"` + + // Duration is the time taken in seconds to run all tests in the suite + Duration float64 `xml:"time,attr"` + + // Properties holds other properties of the test suite as a mapping of name to value + Properties []*TestSuiteProperty `xml:"properties,omitempty"` + + // TestCases are the test cases contained in the test suite + TestCases []*TestCase `xml:"testcases"` + + // Children holds nested test suites + Children []*TestSuite `xml:"testsuites"` //nolint +} + +// TestSuiteProperty contains a mapping of a property name to a value +type TestSuiteProperty struct { + XMLName xml.Name `xml:"properties"` + + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` +} + +// TestCase represents a jUnit test case +type TestCase struct { + XMLName xml.Name `xml:"testcase"` + + // Name is the name of the test case + Name string `xml:"name,attr"` + + // Classname is an attribute set by the package type and is required + Classname string `xml:"classname,attr,omitempty"` + + // Duration is the time taken in seconds to run the test + Duration float64 `xml:"time,attr"` + + // SkipMessage holds the reason why the test was skipped + SkipMessage *SkipMessage `xml:"skipped"` + + // FailureOutput holds the output from a failing test + FailureOutput *FailureOutput `xml:"failure"` + + // SystemOut is output written to stdout during the execution of this test case + SystemOut string `xml:"system-out,omitempty"` + + // SystemErr is output written to stderr during the execution of this test case + SystemErr string `xml:"system-err,omitempty"` +} + +// SkipMessage holds a message explaining why a test was skipped +type SkipMessage struct { + XMLName xml.Name `xml:"skipped"` + + // Message explains why the test was skipped + Message string `xml:"message,attr,omitempty"` +} + +// FailureOutput holds the output from a failing test +type FailureOutput struct { + XMLName xml.Name `xml:"failure"` + + // Message holds the failure message from the test + Message string `xml:"message,attr"` + + // Output holds verbose failure output from the test + Output string `xml:",chardata"` +} + +// TestResult is the result of a test case +type TestResult string diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/LICENSE b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/README.md b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/README.md new file mode 100644 index 000000000..1a5def772 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/README.md @@ -0,0 +1,3 @@ +This package is copy/pasted from [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery/tree/master/pkg/util/sets) +to avoid a circular dependency with `openshift/kubernetes` as it requires OTE and, without having done this, +OTE would require `kubernetes/kubernetes`. diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/byte.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/byte.go new file mode 100644 index 000000000..4d7a17c3a --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/byte.go @@ -0,0 +1,137 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sets + +// Byte is a set of bytes, implemented via map[byte]struct{} for minimal memory consumption. +// +// Deprecated: use generic Set instead. +// new ways: +// s1 := Set[byte]{} +// s2 := New[byte]() +type Byte map[byte]Empty + +// NewByte creates a Byte from a list of values. +func NewByte(items ...byte) Byte { + return Byte(New[byte](items...)) +} + +// ByteKeySet creates a Byte from a keys of a map[byte](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func ByteKeySet[T any](theMap map[byte]T) Byte { + return Byte(KeySet(theMap)) +} + +// Insert adds items to the set. +func (s Byte) Insert(items ...byte) Byte { + return Byte(cast(s).Insert(items...)) +} + +// Delete removes all items from the set. +func (s Byte) Delete(items ...byte) Byte { + return Byte(cast(s).Delete(items...)) +} + +// Has returns true if and only if item is contained in the set. +func (s Byte) Has(item byte) bool { + return cast(s).Has(item) +} + +// HasAll returns true if and only if all items are contained in the set. +func (s Byte) HasAll(items ...byte) bool { + return cast(s).HasAll(items...) +} + +// HasAny returns true if any items are contained in the set. +func (s Byte) HasAny(items ...byte) bool { + return cast(s).HasAny(items...) +} + +// Clone returns a new set which is a copy of the current set. +func (s Byte) Clone() Byte { + return Byte(cast(s).Clone()) +} + +// Difference returns a set of objects that are not in s2. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s1 Byte) Difference(s2 Byte) Byte { + return Byte(cast(s1).Difference(cast(s2))) +} + +// SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.SymmetricDifference(s2) = {a3, a4, a5} +// s2.SymmetricDifference(s1) = {a3, a4, a5} +func (s1 Byte) SymmetricDifference(s2 Byte) Byte { + return Byte(cast(s1).SymmetricDifference(cast(s2))) +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s1 Byte) Union(s2 Byte) Byte { + return Byte(cast(s1).Union(cast(s2))) +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s1 Byte) Intersection(s2 Byte) Byte { + return Byte(cast(s1).Intersection(cast(s2))) +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 Byte) IsSuperset(s2 Byte) bool { + return cast(s1).IsSuperset(cast(s2)) +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 Byte) Equal(s2 Byte) bool { + return cast(s1).Equal(cast(s2)) +} + +// List returns the contents as a sorted byte slice. +func (s Byte) List() []byte { + return List(cast(s)) +} + +// UnsortedList returns the slice with contents in random order. +func (s Byte) UnsortedList() []byte { + return cast(s).UnsortedList() +} + +// PopAny returns a single element from the set. +func (s Byte) PopAny() (byte, bool) { + return cast(s).PopAny() +} + +// Len returns the size of the set. +func (s Byte) Len() int { + return len(s) +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/doc.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/doc.go new file mode 100644 index 000000000..997f5e033 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package sets has generic set and specified sets. Generic set will +// replace specified ones over time. And specific ones are deprecated. +package sets // import "github.com/openshift-eng/openshift-tests-extension/pkg/util/sets" diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/empty.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/empty.go new file mode 100644 index 000000000..fbb1df06d --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/empty.go @@ -0,0 +1,21 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sets + +// Empty is public since it is used by some internal API objects for conversions between external +// string arrays and internal sets, and conversion logic requires public types today. +type Empty struct{} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/int.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/int.go new file mode 100644 index 000000000..5876fc9de --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/int.go @@ -0,0 +1,137 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sets + +// Int is a set of ints, implemented via map[int]struct{} for minimal memory consumption. +// +// Deprecated: use generic Set instead. +// new ways: +// s1 := Set[int]{} +// s2 := New[int]() +type Int map[int]Empty + +// NewInt creates a Int from a list of values. +func NewInt(items ...int) Int { + return Int(New[int](items...)) +} + +// IntKeySet creates a Int from a keys of a map[int](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func IntKeySet[T any](theMap map[int]T) Int { + return Int(KeySet(theMap)) +} + +// Insert adds items to the set. +func (s Int) Insert(items ...int) Int { + return Int(cast(s).Insert(items...)) +} + +// Delete removes all items from the set. +func (s Int) Delete(items ...int) Int { + return Int(cast(s).Delete(items...)) +} + +// Has returns true if and only if item is contained in the set. +func (s Int) Has(item int) bool { + return cast(s).Has(item) +} + +// HasAll returns true if and only if all items are contained in the set. +func (s Int) HasAll(items ...int) bool { + return cast(s).HasAll(items...) +} + +// HasAny returns true if any items are contained in the set. +func (s Int) HasAny(items ...int) bool { + return cast(s).HasAny(items...) +} + +// Clone returns a new set which is a copy of the current set. +func (s Int) Clone() Int { + return Int(cast(s).Clone()) +} + +// Difference returns a set of objects that are not in s2. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s1 Int) Difference(s2 Int) Int { + return Int(cast(s1).Difference(cast(s2))) +} + +// SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.SymmetricDifference(s2) = {a3, a4, a5} +// s2.SymmetricDifference(s1) = {a3, a4, a5} +func (s1 Int) SymmetricDifference(s2 Int) Int { + return Int(cast(s1).SymmetricDifference(cast(s2))) +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s1 Int) Union(s2 Int) Int { + return Int(cast(s1).Union(cast(s2))) +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s1 Int) Intersection(s2 Int) Int { + return Int(cast(s1).Intersection(cast(s2))) +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 Int) IsSuperset(s2 Int) bool { + return cast(s1).IsSuperset(cast(s2)) +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 Int) Equal(s2 Int) bool { + return cast(s1).Equal(cast(s2)) +} + +// List returns the contents as a sorted int slice. +func (s Int) List() []int { + return List(cast(s)) +} + +// UnsortedList returns the slice with contents in random order. +func (s Int) UnsortedList() []int { + return cast(s).UnsortedList() +} + +// PopAny returns a single element from the set. +func (s Int) PopAny() (int, bool) { + return cast(s).PopAny() +} + +// Len returns the size of the set. +func (s Int) Len() int { + return len(s) +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/int32.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/int32.go new file mode 100644 index 000000000..2c640c5d0 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/int32.go @@ -0,0 +1,137 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sets + +// Int32 is a set of int32s, implemented via map[int32]struct{} for minimal memory consumption. +// +// Deprecated: use generic Set instead. +// new ways: +// s1 := Set[int32]{} +// s2 := New[int32]() +type Int32 map[int32]Empty + +// NewInt32 creates a Int32 from a list of values. +func NewInt32(items ...int32) Int32 { + return Int32(New[int32](items...)) +} + +// Int32KeySet creates a Int32 from a keys of a map[int32](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func Int32KeySet[T any](theMap map[int32]T) Int32 { + return Int32(KeySet(theMap)) +} + +// Insert adds items to the set. +func (s Int32) Insert(items ...int32) Int32 { + return Int32(cast(s).Insert(items...)) +} + +// Delete removes all items from the set. +func (s Int32) Delete(items ...int32) Int32 { + return Int32(cast(s).Delete(items...)) +} + +// Has returns true if and only if item is contained in the set. +func (s Int32) Has(item int32) bool { + return cast(s).Has(item) +} + +// HasAll returns true if and only if all items are contained in the set. +func (s Int32) HasAll(items ...int32) bool { + return cast(s).HasAll(items...) +} + +// HasAny returns true if any items are contained in the set. +func (s Int32) HasAny(items ...int32) bool { + return cast(s).HasAny(items...) +} + +// Clone returns a new set which is a copy of the current set. +func (s Int32) Clone() Int32 { + return Int32(cast(s).Clone()) +} + +// Difference returns a set of objects that are not in s2. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s1 Int32) Difference(s2 Int32) Int32 { + return Int32(cast(s1).Difference(cast(s2))) +} + +// SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.SymmetricDifference(s2) = {a3, a4, a5} +// s2.SymmetricDifference(s1) = {a3, a4, a5} +func (s1 Int32) SymmetricDifference(s2 Int32) Int32 { + return Int32(cast(s1).SymmetricDifference(cast(s2))) +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s1 Int32) Union(s2 Int32) Int32 { + return Int32(cast(s1).Union(cast(s2))) +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s1 Int32) Intersection(s2 Int32) Int32 { + return Int32(cast(s1).Intersection(cast(s2))) +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 Int32) IsSuperset(s2 Int32) bool { + return cast(s1).IsSuperset(cast(s2)) +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 Int32) Equal(s2 Int32) bool { + return cast(s1).Equal(cast(s2)) +} + +// List returns the contents as a sorted int32 slice. +func (s Int32) List() []int32 { + return List(cast(s)) +} + +// UnsortedList returns the slice with contents in random order. +func (s Int32) UnsortedList() []int32 { + return cast(s).UnsortedList() +} + +// PopAny returns a single element from the set. +func (s Int32) PopAny() (int32, bool) { + return cast(s).PopAny() +} + +// Len returns the size of the set. +func (s Int32) Len() int { + return len(s) +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/int64.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/int64.go new file mode 100644 index 000000000..bf3eb3ffa --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/int64.go @@ -0,0 +1,137 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sets + +// Int64 is a set of int64s, implemented via map[int64]struct{} for minimal memory consumption. +// +// Deprecated: use generic Set instead. +// new ways: +// s1 := Set[int64]{} +// s2 := New[int64]() +type Int64 map[int64]Empty + +// NewInt64 creates a Int64 from a list of values. +func NewInt64(items ...int64) Int64 { + return Int64(New[int64](items...)) +} + +// Int64KeySet creates a Int64 from a keys of a map[int64](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func Int64KeySet[T any](theMap map[int64]T) Int64 { + return Int64(KeySet(theMap)) +} + +// Insert adds items to the set. +func (s Int64) Insert(items ...int64) Int64 { + return Int64(cast(s).Insert(items...)) +} + +// Delete removes all items from the set. +func (s Int64) Delete(items ...int64) Int64 { + return Int64(cast(s).Delete(items...)) +} + +// Has returns true if and only if item is contained in the set. +func (s Int64) Has(item int64) bool { + return cast(s).Has(item) +} + +// HasAll returns true if and only if all items are contained in the set. +func (s Int64) HasAll(items ...int64) bool { + return cast(s).HasAll(items...) +} + +// HasAny returns true if any items are contained in the set. +func (s Int64) HasAny(items ...int64) bool { + return cast(s).HasAny(items...) +} + +// Clone returns a new set which is a copy of the current set. +func (s Int64) Clone() Int64 { + return Int64(cast(s).Clone()) +} + +// Difference returns a set of objects that are not in s2. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s1 Int64) Difference(s2 Int64) Int64 { + return Int64(cast(s1).Difference(cast(s2))) +} + +// SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.SymmetricDifference(s2) = {a3, a4, a5} +// s2.SymmetricDifference(s1) = {a3, a4, a5} +func (s1 Int64) SymmetricDifference(s2 Int64) Int64 { + return Int64(cast(s1).SymmetricDifference(cast(s2))) +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s1 Int64) Union(s2 Int64) Int64 { + return Int64(cast(s1).Union(cast(s2))) +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s1 Int64) Intersection(s2 Int64) Int64 { + return Int64(cast(s1).Intersection(cast(s2))) +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 Int64) IsSuperset(s2 Int64) bool { + return cast(s1).IsSuperset(cast(s2)) +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 Int64) Equal(s2 Int64) bool { + return cast(s1).Equal(cast(s2)) +} + +// List returns the contents as a sorted int64 slice. +func (s Int64) List() []int64 { + return List(cast(s)) +} + +// UnsortedList returns the slice with contents in random order. +func (s Int64) UnsortedList() []int64 { + return cast(s).UnsortedList() +} + +// PopAny returns a single element from the set. +func (s Int64) PopAny() (int64, bool) { + return cast(s).PopAny() +} + +// Len returns the size of the set. +func (s Int64) Len() int { + return len(s) +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/set.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/set.go new file mode 100644 index 000000000..cd961c8c5 --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/set.go @@ -0,0 +1,236 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sets + +import ( + "cmp" + "sort" +) + +// Set is a set of the same type elements, implemented via map[comparable]struct{} for minimal memory consumption. +type Set[T comparable] map[T]Empty + +// cast transforms specified set to generic Set[T]. +func cast[T comparable](s map[T]Empty) Set[T] { return s } + +// New creates a Set from a list of values. +// NOTE: type param must be explicitly instantiated if given items are empty. +func New[T comparable](items ...T) Set[T] { + ss := make(Set[T], len(items)) + ss.Insert(items...) + return ss +} + +// KeySet creates a Set from a keys of a map[comparable](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func KeySet[T comparable, V any](theMap map[T]V) Set[T] { + ret := make(Set[T], len(theMap)) + for keyValue := range theMap { + ret.Insert(keyValue) + } + return ret +} + +// Insert adds items to the set. +func (s Set[T]) Insert(items ...T) Set[T] { + for _, item := range items { + s[item] = Empty{} + } + return s +} + +func Insert[T comparable](set Set[T], items ...T) Set[T] { + return set.Insert(items...) +} + +// Delete removes all items from the set. +func (s Set[T]) Delete(items ...T) Set[T] { + for _, item := range items { + delete(s, item) + } + return s +} + +// Clear empties the set. +// It is preferable to replace the set with a newly constructed set, +// but not all callers can do that (when there are other references to the map). +func (s Set[T]) Clear() Set[T] { + clear(s) + return s +} + +// Has returns true if and only if item is contained in the set. +func (s Set[T]) Has(item T) bool { + _, contained := s[item] + return contained +} + +// HasAll returns true if and only if all items are contained in the set. +func (s Set[T]) HasAll(items ...T) bool { + for _, item := range items { + if !s.Has(item) { + return false + } + } + return true +} + +// HasAny returns true if any items are contained in the set. +func (s Set[T]) HasAny(items ...T) bool { + for _, item := range items { + if s.Has(item) { + return true + } + } + return false +} + +// Clone returns a new set which is a copy of the current set. +func (s Set[T]) Clone() Set[T] { + result := make(Set[T], len(s)) + for key := range s { + result.Insert(key) + } + return result +} + +// Difference returns a set of objects that are not in s2. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s1 Set[T]) Difference(s2 Set[T]) Set[T] { + result := New[T]() + for key := range s1 { + if !s2.Has(key) { + result.Insert(key) + } + } + return result +} + +// SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.SymmetricDifference(s2) = {a3, a4, a5} +// s2.SymmetricDifference(s1) = {a3, a4, a5} +func (s1 Set[T]) SymmetricDifference(s2 Set[T]) Set[T] { + return s1.Difference(s2).Union(s2.Difference(s1)) +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s1 Set[T]) Union(s2 Set[T]) Set[T] { + result := s1.Clone() + for key := range s2 { + result.Insert(key) + } + return result +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s1 Set[T]) Intersection(s2 Set[T]) Set[T] { + var walk, other Set[T] + result := New[T]() + if s1.Len() < s2.Len() { + walk = s1 + other = s2 + } else { + walk = s2 + other = s1 + } + for key := range walk { + if other.Has(key) { + result.Insert(key) + } + } + return result +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 Set[T]) IsSuperset(s2 Set[T]) bool { + for item := range s2 { + if !s1.Has(item) { + return false + } + } + return true +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 Set[T]) Equal(s2 Set[T]) bool { + return len(s1) == len(s2) && s1.IsSuperset(s2) +} + +type sortableSliceOfGeneric[T cmp.Ordered] []T + +func (g sortableSliceOfGeneric[T]) Len() int { return len(g) } +func (g sortableSliceOfGeneric[T]) Less(i, j int) bool { return less[T](g[i], g[j]) } +func (g sortableSliceOfGeneric[T]) Swap(i, j int) { g[i], g[j] = g[j], g[i] } + +// List returns the contents as a sorted T slice. +// +// This is a separate function and not a method because not all types supported +// by Generic are ordered and only those can be sorted. +func List[T cmp.Ordered](s Set[T]) []T { + res := make(sortableSliceOfGeneric[T], 0, len(s)) + for key := range s { + res = append(res, key) + } + sort.Sort(res) + return res +} + +// UnsortedList returns the slice with contents in random order. +func (s Set[T]) UnsortedList() []T { + res := make([]T, 0, len(s)) + for key := range s { + res = append(res, key) + } + return res +} + +// PopAny returns a single element from the set. +func (s Set[T]) PopAny() (T, bool) { + for key := range s { + s.Delete(key) + return key, true + } + var zeroValue T + return zeroValue, false +} + +// Len returns the size of the set. +func (s Set[T]) Len() int { + return len(s) +} + +func less[T cmp.Ordered](lhs, rhs T) bool { + return lhs < rhs +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/string.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/string.go new file mode 100644 index 000000000..1dab6d13c --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/string.go @@ -0,0 +1,137 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sets + +// String is a set of strings, implemented via map[string]struct{} for minimal memory consumption. +// +// Deprecated: use generic Set instead. +// new ways: +// s1 := Set[string]{} +// s2 := New[string]() +type String map[string]Empty + +// NewString creates a String from a list of values. +func NewString(items ...string) String { + return String(New[string](items...)) +} + +// StringKeySet creates a String from a keys of a map[string](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func StringKeySet[T any](theMap map[string]T) String { + return String(KeySet(theMap)) +} + +// Insert adds items to the set. +func (s String) Insert(items ...string) String { + return String(cast(s).Insert(items...)) +} + +// Delete removes all items from the set. +func (s String) Delete(items ...string) String { + return String(cast(s).Delete(items...)) +} + +// Has returns true if and only if item is contained in the set. +func (s String) Has(item string) bool { + return cast(s).Has(item) +} + +// HasAll returns true if and only if all items are contained in the set. +func (s String) HasAll(items ...string) bool { + return cast(s).HasAll(items...) +} + +// HasAny returns true if any items are contained in the set. +func (s String) HasAny(items ...string) bool { + return cast(s).HasAny(items...) +} + +// Clone returns a new set which is a copy of the current set. +func (s String) Clone() String { + return String(cast(s).Clone()) +} + +// Difference returns a set of objects that are not in s2. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s1 String) Difference(s2 String) String { + return String(cast(s1).Difference(cast(s2))) +} + +// SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.SymmetricDifference(s2) = {a3, a4, a5} +// s2.SymmetricDifference(s1) = {a3, a4, a5} +func (s1 String) SymmetricDifference(s2 String) String { + return String(cast(s1).SymmetricDifference(cast(s2))) +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s1 String) Union(s2 String) String { + return String(cast(s1).Union(cast(s2))) +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s1 String) Intersection(s2 String) String { + return String(cast(s1).Intersection(cast(s2))) +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 String) IsSuperset(s2 String) bool { + return cast(s1).IsSuperset(cast(s2)) +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 String) Equal(s2 String) bool { + return cast(s1).Equal(cast(s2)) +} + +// List returns the contents as a sorted string slice. +func (s String) List() []string { + return List(cast(s)) +} + +// UnsortedList returns the slice with contents in random order. +func (s String) UnsortedList() []string { + return cast(s).UnsortedList() +} + +// PopAny returns a single element from the set. +func (s String) PopAny() (string, bool) { + return cast(s).PopAny() +} + +// Len returns the size of the set. +func (s String) Len() int { + return len(s) +} diff --git a/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/version/version.go b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/version/version.go new file mode 100644 index 000000000..7d6a3309b --- /dev/null +++ b/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/version/version.go @@ -0,0 +1,11 @@ +package version + +var ( + // CommitFromGit is a constant representing the source version that + // generated this build. It should be set during build via -ldflags. + CommitFromGit string + // BuildDate in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') + BuildDate string + // GitTreeState has the state of git tree, either "clean" or "dirty" + GitTreeState string +) diff --git a/vendor/modules.txt b/vendor/modules.txt index 10ab4f861..92ee9ac61 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -818,6 +818,22 @@ github.com/opencontainers/runtime-spec/specs-go github.com/opencontainers/selinux/go-selinux github.com/opencontainers/selinux/go-selinux/label github.com/opencontainers/selinux/pkg/pwalkdir +# github.com/openshift-eng/openshift-tests-extension v0.0.0-20260422120742-50dec0b5ecf3 +## explicit; go 1.23.0 +github.com/openshift-eng/openshift-tests-extension/pkg/cmd +github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdimages +github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdinfo +github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdlist +github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun +github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdupdate +github.com/openshift-eng/openshift-tests-extension/pkg/dbtime +github.com/openshift-eng/openshift-tests-extension/pkg/extension +github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests +github.com/openshift-eng/openshift-tests-extension/pkg/flags +github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo +github.com/openshift-eng/openshift-tests-extension/pkg/junit +github.com/openshift-eng/openshift-tests-extension/pkg/util/sets +github.com/openshift-eng/openshift-tests-extension/pkg/version # github.com/openshift/api v0.0.0-20250513132935-9052dea86694 ## explicit; go 1.23.0 github.com/openshift/api @@ -2831,7 +2847,7 @@ k8s.io/klog/v2/internal/sloghandler k8s.io/klog/v2/internal/verbosity k8s.io/klog/v2/ktesting k8s.io/klog/v2/textlogger -# k8s.io/kms v0.32.1 +# k8s.io/kms v0.32.1 => github.com/openshift/kubernetes/staging/src/k8s.io/kms v0.0.0-20250527023356-4cd5657aac98 ## explicit; go 1.23.0 k8s.io/kms/apis/v1beta1 k8s.io/kms/apis/v2 @@ -4114,6 +4130,7 @@ sigs.k8s.io/yaml/goyaml.v3 # k8s.io/dynamic-resource-allocation => github.com/openshift/kubernetes/staging/src/k8s.io/dynamic-resource-allocation v0.0.0-20250527023356-4cd5657aac98 # k8s.io/endpointslice => github.com/openshift/kubernetes/staging/src/k8s.io/endpointslice v0.0.0-20250527023356-4cd5657aac98 # k8s.io/externaljwt => github.com/openshift/kubernetes/staging/src/k8s.io/externaljwt v0.0.0-20250527023356-4cd5657aac98 +# k8s.io/kms => github.com/openshift/kubernetes/staging/src/k8s.io/kms v0.0.0-20250527023356-4cd5657aac98 # k8s.io/kube-aggregator => github.com/openshift/kubernetes/staging/src/k8s.io/kube-aggregator v0.0.0-20250527023356-4cd5657aac98 # k8s.io/kube-controller-manager => github.com/openshift/kubernetes/staging/src/k8s.io/kube-controller-manager v0.0.0-20250527023356-4cd5657aac98 # k8s.io/kube-proxy => github.com/openshift/kubernetes/staging/src/k8s.io/kube-proxy v0.0.0-20250527023356-4cd5657aac98