diff --git a/cmd/cloud-controller-manager-aws-tests-ext/go.mod b/cmd/cloud-controller-manager-aws-tests-ext/go.mod
index bdf63933b..73e32bd8e 100644
--- a/cmd/cloud-controller-manager-aws-tests-ext/go.mod
+++ b/cmd/cloud-controller-manager-aws-tests-ext/go.mod
@@ -9,7 +9,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.45.2
github.com/onsi/ginkgo/v2 v2.27.2
github.com/onsi/gomega v1.38.2
- github.com/openshift-eng/openshift-tests-extension v0.0.0-20250916161632-d81c09058835
+ github.com/openshift-eng/openshift-tests-extension v0.0.0-20260626105913-1f81f3df939a
github.com/openshift/client-go v0.0.0-20260108185524-48f4ccfc4e13
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.10.1
@@ -17,6 +17,7 @@ require (
k8s.io/apimachinery v0.35.0
k8s.io/client-go v0.35.0
k8s.io/cloud-provider-aws/tests/e2e v0.0.0-20260227223131-ea961d6fafc4
+ k8s.io/klog/v2 v2.130.1
k8s.io/kubernetes v1.35.0
k8s.io/pod-security-admission v0.35.0
)
@@ -88,7 +89,6 @@ require (
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/openshift/api v0.0.0-20260209232644-126cbbe24427 // indirect
- github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
@@ -133,7 +133,6 @@ require (
k8s.io/component-base v0.35.0 // indirect
k8s.io/component-helpers v0.35.0 // indirect
k8s.io/controller-manager v0.35.0 // indirect
- k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
k8s.io/kubectl v0.33.0 // indirect
k8s.io/kubelet v0.35.0 // indirect
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/go.sum b/cmd/cloud-controller-manager-aws-tests-ext/go.sum
index 334c23526..a31e51d9b 100644
--- a/cmd/cloud-controller-manager-aws-tests-ext/go.sum
+++ b/cmd/cloud-controller-manager-aws-tests-ext/go.sum
@@ -141,16 +141,14 @@ github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
-github.com/openshift-eng/openshift-tests-extension v0.0.0-20250916161632-d81c09058835 h1:rkqIIfdYYkasXbF2XKVgh/3f1mhjSQK9By8WtVMgYo8=
-github.com/openshift-eng/openshift-tests-extension v0.0.0-20250916161632-d81c09058835/go.mod h1:6gkP5f2HL0meusT0Aim8icAspcD1cG055xxBZ9yC68M=
+github.com/openshift-eng/openshift-tests-extension v0.0.0-20260626105913-1f81f3df939a h1:9kpy75Nbn/TZV8N7mLh1euQNrX5DFASwjTWzKzIPUnI=
+github.com/openshift-eng/openshift-tests-extension v0.0.0-20260626105913-1f81f3df939a/go.mod h1:pHOS9c6BjZv91OkkHyIHAOWnYhxwcxWQkyYGEvPyUCE=
github.com/openshift/api v0.0.0-20260209232644-126cbbe24427 h1:MExw+yvWGmbwlTpsO8sk16n3YQeeE2QxLmLpQouIGeE=
github.com/openshift/api v0.0.0-20260209232644-126cbbe24427/go.mod h1:d5uzF0YN2nQQFA0jIEWzzOZ+edmo6wzlGLvx5Fhz4uY=
github.com/openshift/client-go v0.0.0-20260108185524-48f4ccfc4e13 h1:6rd4zSo2UaWQcAPZfHK9yzKVqH0BnMv1hqMzqXZyTds=
github.com/openshift/client-go v0.0.0-20260108185524-48f4ccfc4e13/go.mod h1:YvOmPmV7wcJxpfhTDuFqqs2Xpb3M3ovsM6Qs/i2ptq4=
github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20260303184444-1cc650aa0565 h1:3/q8qM4HbFa+Een8wgzpwO8W6mO7Po+MwY6uxiXi/ac=
github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20260303184444-1cc650aa0565/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/main.go b/cmd/cloud-controller-manager-aws-tests-ext/main.go
index d17b2813f..07d1f82ff 100644
--- a/cmd/cloud-controller-manager-aws-tests-ext/main.go
+++ b/cmd/cloud-controller-manager-aws-tests-ext/main.go
@@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"strings"
+ "syscall"
"github.com/openshift-eng/openshift-tests-extension/pkg/cmd"
e "github.com/openshift-eng/openshift-tests-extension/pkg/extension"
@@ -15,6 +16,7 @@ import (
corev1 "k8s.io/api/core/v1"
kclientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
+ klog "k8s.io/klog/v2"
"k8s.io/kubernetes/test/e2e/framework"
log "github.com/sirupsen/logrus"
@@ -32,7 +34,49 @@ var (
isDualStackPrimaryIpv6 bool
)
+// redirectStdoutToStderr moves file descriptor 1 (stdout) so that it
+// points to stderr, keeping stdout clean for JSON output expected by
+// openshift-tests-extension during "list".
+//
+// The Ginkgo init() (core_dsl.go) captures os.Stdout and wires it into
+// GinkgoWriter *before* main() runs. Any code that writes through
+// GinkgoWriter (framework.Logf, klog via the ginkgo text-logger, etc.)
+// therefore writes to fd 1. Simply reassigning os.Stdout does not help
+// because GinkgoWriter already holds the original *os.File.
+//
+// To solve this we:
+// 1. Dup fd 1 → savedFd (preserve the real stdout)
+// 2. Dup2 fd 2 → fd 1 (fd 1 now points to stderr)
+// 3. os.Stdout = NewFile(savedFd) (so OTE's explicit os.Stdout writes
+// still reach the real stdout)
+//
+// After this, anything that writes to the *original* os.Stdout (fd 1)
+// — including GinkgoWriter — actually goes to stderr, while code that
+// references the os.Stdout variable (OTE list/info commands) goes to
+// the real stdout via savedFd.
+func redirectStdoutToStderr() {
+ savedFd, err := syscall.Dup(int(os.Stdout.Fd()))
+ if err != nil {
+ // If we can't dup, fall through — best-effort.
+ fmt.Fprintf(os.Stderr, "warning: failed to dup stdout: %v\n", err)
+ return
+ }
+ if err := syscall.Dup2(int(os.Stderr.Fd()), int(os.Stdout.Fd())); err != nil {
+ fmt.Fprintf(os.Stderr, "warning: failed to redirect stdout to stderr: %v\n", err)
+ syscall.Close(savedFd)
+ return
+ }
+ os.Stdout = os.NewFile(uintptr(savedFd), "/dev/stdout")
+}
+
func main() {
+ // Redirect fd 1 to stderr so that framework/klog/ginkgo log output
+ // does not corrupt the JSON stream expected by OTE on stdout.
+ redirectStdoutToStderr()
+
+ // Belt-and-suspenders: explicitly send klog output to stderr.
+ klog.SetOutput(os.Stderr)
+
registry := e.NewRegistry()
ext := e.NewExtension("openshift", "payload", "aws-cloud-controller-manager")
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun/runsuite.go b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun/runsuite.go
index d81d07cb2..a30b05e86 100644
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun/runsuite.go
+++ b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun/runsuite.go
@@ -2,13 +2,15 @@ package cmdrun
import (
"context"
+ "encoding/json"
+ "errors"
"fmt"
"os"
"os/signal"
+ "path/filepath"
"syscall"
"time"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/openshift-eng/openshift-tests-extension/pkg/extension"
@@ -22,11 +24,13 @@ func NewRunSuiteCommand(registry *extension.Registry) *cobra.Command {
outputFlags *flags.OutputFlags
concurrencyFlags *flags.ConcurrencyFlags
junitPath string
+ htmlPath string
}{
componentFlags: flags.NewComponentFlags(),
outputFlags: flags.NewOutputFlags(),
concurrencyFlags: flags.NewConcurrencyFlags(),
junitPath: "",
+ htmlPath: "",
}
cmd := &cobra.Command{
@@ -70,7 +74,7 @@ func NewRunSuiteCommand(registry *extension.Registry) *cobra.Command {
}
suite, err := ext.GetSuite(args[0])
if err != nil {
- return errors.Wrapf(err, "couldn't find suite: %s", args[0])
+ return fmt.Errorf("couldn't find suite %q: %w", args[0], err)
}
compositeWriter := extensiontests.NewCompositeResultWriter()
@@ -84,10 +88,18 @@ func NewRunSuiteCommand(registry *extension.Registry) *cobra.Command {
if opts.junitPath != "" {
junitWriter, err := extensiontests.NewJUnitResultWriter(opts.junitPath, suite.Name)
if err != nil {
- return errors.Wrap(err, "couldn't create junit writer")
+ 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,
@@ -99,16 +111,51 @@ func NewRunSuiteCommand(registry *extension.Registry) *cobra.Command {
specs, err := ext.GetSpecs().Filter(suite.Qualifiers)
if err != nil {
- return errors.Wrap(err, "couldn't filter specs")
+ return fmt.Errorf("couldn't filter specs: %w", err)
}
- return specs.Run(ctx, compositeWriter, opts.concurrencyFlags.MaxConcurency)
+ 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/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun/runtest.go b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun/runtest.go
index c06894ed9..86e10e02e 100644
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun/runtest.go
+++ b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun/runtest.go
@@ -3,9 +3,9 @@ package cmdrun
import (
"bufio"
"context"
+ "errors"
"fmt"
"os"
- "errors"
"os/signal"
"syscall"
"time"
@@ -23,6 +23,7 @@ func NewRunTestCommand(registry *extension.Registry) *cobra.Command {
concurrencyFlags *flags.ConcurrencyFlags
nameFlags *flags.NamesFlags
outputFlags *flags.OutputFlags
+ timeout time.Duration
}{
componentFlags: flags.NewComponentFlags(),
nameFlags: flags.NewNamesFlags(),
@@ -37,6 +38,11 @@ func NewRunTestCommand(registry *extension.Registry) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancelCause := context.WithCancelCause(context.Background())
defer cancelCause(errors.New("exiting"))
+ if opts.timeout > 0 {
+ var cancel context.CancelFunc
+ ctx, cancel = context.WithTimeout(ctx, opts.timeout)
+ defer cancel()
+ }
abortCh := make(chan os.Signal, 2)
go func() {
@@ -100,9 +106,11 @@ func NewRunTestCommand(registry *extension.Registry) *cobra.Command {
}
defer w.Flush()
- return specs.Run(ctx, w, opts.concurrencyFlags.MaxConcurency)
+ _, err = specs.Run(ctx, w, opts.concurrencyFlags.MaxConcurency)
+ return err
},
}
+ cmd.Flags().DurationVar(&opts.timeout, "timeout", 0, "Maximum duration for the test. When set, the test context will have a deadline, causing blocking operations like PollUntilDone to fail cleanly instead of hanging until the parent kills the process.")
opts.componentFlags.BindFlags(cmd.Flags())
opts.nameFlags.BindFlags(cmd.Flags())
opts.outputFlags.BindFlags(cmd.Flags())
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/result.go b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/result.go
index 2e36969fe..9c03a0a84 100644
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/result.go
+++ b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/result.go
@@ -1,8 +1,12 @@
package extensiontests
import (
+ "bytes"
+ _ "embed"
+ "encoding/json"
"fmt"
"strings"
+ "text/template"
"github.com/openshift-eng/openshift-tests-extension/pkg/junit"
)
@@ -67,3 +71,55 @@ func (results ExtensionTestResults) ToJUnit(suiteName string) junit.TestSuite {
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/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/result_writer.go b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/result_writer.go
index aedc409c1..f9ca434ca 100644
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/result_writer.go
+++ b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/result_writer.go
@@ -124,8 +124,9 @@ func NewJSONResultWriter(out io.Writer, format ResultFormat) (*JSONResultWriter,
}
return &JSONResultWriter{
- out: out,
- format: format,
+ out: out,
+ format: format,
+ results: ExtensionTestResults{},
}, nil
}
@@ -162,3 +163,51 @@ func (w *JSONResultWriter) Flush() error {
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/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/scheduler.go b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/scheduler.go
new file mode 100644
index 000000000..ea4e08182
--- /dev/null
+++ b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/scheduler.go
@@ -0,0 +1,199 @@
+package extensiontests
+
+import (
+ "context"
+ "sync"
+
+ "github.com/openshift-eng/openshift-tests-extension/pkg/util/sets"
+)
+
+const defaultConflictGroup = "default"
+
+// Scheduler defines the interface for test scheduling.
+// It manages scheduling based on isolation requirements (conflicts, taints, tolerations).
+//
+// Callers must follow a get-once, complete-once protocol: every non-nil spec returned by
+// GetNextTestToRun must eventually be passed to MarkTestComplete exactly once, including
+// when test execution panics.
+type Scheduler interface {
+ // GetNextTestToRun blocks until a test is available, then returns it.
+ // Returns nil when all tests have been distributed (queue is empty) or context is cancelled.
+ // When a test is returned, it is atomically removed from queue and marked as running.
+ // This method can be safely called from multiple goroutines concurrently.
+ GetNextTestToRun(ctx context.Context) *ExtensionTestSpec
+
+ // MarkTestComplete marks a test as complete, cleaning up its conflicts and taints.
+ // This may unblock other tests that were waiting.
+ // This method can be safely called from multiple goroutines concurrently.
+ MarkTestComplete(spec *ExtensionTestSpec)
+}
+
+// testScheduler manages test scheduling based on conflicts, taints, and tolerations.
+// It maintains an ordered queue of tests and provides thread-safe scheduling operations.
+type testScheduler struct {
+ mu sync.Mutex
+ cond *sync.Cond // condition variable to signal when tests complete
+ tests []*ExtensionTestSpec
+ runningConflicts map[string]sets.Set[string] // tracks which conflicts are running per group: group -> set of conflicts
+ activeTaints map[string]int // tracks how many tests are currently applying each taint
+}
+
+// NewScheduler creates a test scheduler. It accepts tests in any order and schedules
+// them based on isolation requirements (conflicts, taints, tolerations).
+func NewScheduler(tests []*ExtensionTestSpec) Scheduler {
+ ts := &testScheduler{
+ tests: append([]*ExtensionTestSpec(nil), tests...),
+ runningConflicts: make(map[string]sets.Set[string]),
+ activeTaints: make(map[string]int),
+ }
+ ts.cond = sync.NewCond(&ts.mu)
+ return ts
+}
+
+// GetNextTestToRun blocks until a test is available to run, or returns nil
+// if all tests have been distributed or the context is cancelled.
+// It continuously scans the queue and waits for state changes when no tests are runnable.
+// When a test is returned, it is atomically removed from queue and marked as running.
+func (ts *testScheduler) GetNextTestToRun(ctx context.Context) *ExtensionTestSpec {
+ ts.mu.Lock()
+ defer ts.mu.Unlock()
+
+ // Set up context cancellation to wake up any waiting goroutine
+ done := make(chan struct{})
+ defer close(done)
+ go func() {
+ select {
+ case <-ctx.Done():
+ ts.mu.Lock()
+ ts.cond.Broadcast()
+ ts.mu.Unlock()
+ case <-done:
+ // Normal exit, nothing to do
+ }
+ }()
+
+ for {
+ // Check if context is cancelled
+ if ctx.Err() != nil {
+ return nil
+ }
+
+ // Check if all tests have been distributed
+ if len(ts.tests) == 0 {
+ return nil
+ }
+
+ // Scan from beginning to find first runnable test
+ for i, spec := range ts.tests {
+ conflictGroup := getConflictGroup(spec)
+
+ // Ensure the conflict group set exists
+ if ts.runningConflicts[conflictGroup] == nil {
+ ts.runningConflicts[conflictGroup] = sets.New[string]()
+ }
+
+ // Check if any of the test's conflicts are currently running within its group
+ hasConflict := ts.hasActiveConflict(spec, conflictGroup)
+
+ // Check if test can tolerate all currently active taints
+ canTolerate := ts.canTolerateTaints(spec)
+
+ if !hasConflict && canTolerate {
+ isolation := &spec.Resources.Isolation
+
+ // Found a runnable test - ATOMICALLY:
+ // 1. Mark conflicts as running
+ for _, conflict := range isolation.Conflict {
+ ts.runningConflicts[conflictGroup].Insert(conflict)
+ }
+
+ // 2. Activate taints
+ for _, taint := range isolation.Taint {
+ ts.activeTaints[taint]++
+ }
+
+ // 3. Remove test from queue
+ ts.tests = append(ts.tests[:i], ts.tests[i+1:]...)
+
+ // 4. Return the test (now safe to run)
+ return spec
+ }
+ }
+
+ // No runnable test found, but tests still exist in queue - wait for state change
+ ts.cond.Wait()
+ }
+}
+
+func getConflictGroup(_ *ExtensionTestSpec) string {
+ return defaultConflictGroup
+}
+
+// hasActiveConflict checks if the spec has any conflicts with currently running tests.
+func (ts *testScheduler) hasActiveConflict(spec *ExtensionTestSpec, conflictGroup string) bool {
+ for _, conflict := range spec.Resources.Isolation.Conflict {
+ if ts.runningConflicts[conflictGroup].Has(conflict) {
+ return true
+ }
+ }
+ return false
+}
+
+// canTolerateTaints checks if a spec can tolerate all currently active taints.
+func (ts *testScheduler) canTolerateTaints(spec *ExtensionTestSpec) bool {
+ // If no taints are active, any test can run
+ if len(ts.activeTaints) == 0 {
+ return true
+ }
+
+ // Build a set of tolerations for efficient lookup
+ tolerations := sets.New(spec.Resources.Isolation.Toleration...)
+
+ // Check if test tolerates all active taints
+ for taint, count := range ts.activeTaints {
+ // Skip taints with zero count (should be cleaned up but being defensive)
+ if count <= 0 {
+ continue
+ }
+
+ if !tolerations.Has(taint) {
+ return false // Test cannot tolerate this active taint
+ }
+ }
+ return true
+}
+
+// MarkTestComplete marks all conflicts and taints of a spec as no longer running/active
+// and signals waiting workers that blocked tests may now be runnable.
+// This should be called after a test completes execution.
+func (ts *testScheduler) MarkTestComplete(spec *ExtensionTestSpec) {
+ ts.mu.Lock()
+ defer ts.mu.Unlock()
+
+ if spec == nil {
+ ts.cond.Broadcast()
+ return
+ }
+
+ isolation := &spec.Resources.Isolation
+ conflictGroup := getConflictGroup(spec)
+
+ // Clean up conflicts within this group
+ if groupConflicts, exists := ts.runningConflicts[conflictGroup]; exists {
+ for _, conflict := range isolation.Conflict {
+ groupConflicts.Delete(conflict)
+ }
+ }
+
+ // Clean up taints with reference counting
+ for _, taint := range isolation.Taint {
+ ts.activeTaints[taint]--
+ if ts.activeTaints[taint] <= 0 {
+ delete(ts.activeTaints, taint)
+ }
+ }
+
+ // Signal waiting workers that the state has changed
+ // Some blocked tests might now be runnable
+ ts.cond.Broadcast()
+}
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/spec.go b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/spec.go
index 4ac540dc2..56cdc0fde 100644
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/spec.go
+++ b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/spec.go
@@ -3,6 +3,7 @@ package extensiontests
import (
"context"
"fmt"
+ "os"
"strings"
"sync"
"sync/atomic"
@@ -106,18 +107,32 @@ func (specs ExtensionTestSpecs) MustSelectAll(selectFns []SelectFunction) (Exten
return filtered, nil
}
-// ModuleTestsOnly ensures that ginkgo tests from vendored sources aren't selected,
-// except for the Origin extended util packages, that may contain Ginkgo nodes but
-// should not cause a test exclusion.
+// 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 {
- if strings.Contains(cl, "/vendor/") && !strings.Contains(cl, "github.com/openshift/origin/test/extended/util") {
- return false
+ // 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
}
}
- return true
+ // Include the test only if it has at least one local code location
+ return hasLocalCode
}
}
@@ -181,9 +196,12 @@ func (specs ExtensionTestSpecs) Names() []string {
// 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) error {
- queue := make(chan *ExtensionTestSpec)
- failures := atomic.Int64{}
+//
+// Tests are scheduled using isolation-aware scheduling that respects conflicts, taints, and
+// tolerations defined in each spec's Resources.Isolation field.
+func (specs ExtensionTestSpecs) Run(ctx context.Context, w ResultWriter, maxConcurrent int) ([]*ExtensionTestResult, error) {
+ terminalFailures := atomic.Int64{}
+ nonTerminalFailures := atomic.Int64{}
// Execute beforeAll
for _, spec := range specs {
@@ -192,50 +210,62 @@ func (specs ExtensionTestSpecs) Run(ctx context.Context, w ResultWriter, maxConc
}
}
- // 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
+ // Create scheduler with isolation-aware scheduling
+ scheduler := NewScheduler(specs)
+
// 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)
+ for {
+ // Get next runnable test from scheduler (blocks until available or done)
+ spec := scheduler.GetNextTestToRun(ctx)
+ if spec == nil {
+ return // No more tests or context cancelled
}
- res := runSpec(ctx, spec, runSingleSpec)
- if res.Result == ResultFailed {
- failures.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)
+ func() {
+ defer scheduler.MarkTestComplete(spec)
+
+ 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 {
@@ -244,11 +274,28 @@ func (specs ExtensionTestSpecs) Run(ctx context.Context, w ResultWriter, maxConc
}
}
- failCount := failures.Load()
- if failCount > 0 {
- return fmt.Errorf("%d tests failed", failCount)
+ var results []*ExtensionTestResult
+ for res := range resultChan {
+ results = append(results, res)
}
- return nil
+
+ 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.
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/types.go b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/types.go
index a1143cc71..f3edf41a6 100644
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/types.go
+++ b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/types.go
@@ -2,6 +2,7 @@ package extensiontests
import (
"context"
+ "time"
"github.com/openshift-eng/openshift-tests-extension/pkg/dbtime"
"github.com/openshift-eng/openshift-tests-extension/pkg/util/sets"
@@ -12,6 +13,12 @@ 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 {
@@ -53,6 +60,10 @@ type ExtensionTestSpec struct {
// 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
@@ -68,8 +79,10 @@ type Resources struct {
}
type Isolation struct {
- Mode string `json:"mode,omitempty"`
- Conflict []string `json:"conflict,omitempty"`
+ Mode string `json:"mode,omitempty"`
+ Conflict []string `json:"conflict,omitempty"`
+ Taint []string `json:"taint,omitempty"`
+ Toleration []string `json:"toleration,omitempty"`
}
type EnvironmentSelector struct {
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/viewer.html b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/viewer.html
new file mode 100644
index 000000000..2ff236aa3
--- /dev/null
+++ b/cmd/cloud-controller-manager-aws-tests-ext/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
+
+
+
+
+
+
+
0
+
Total
+
+
+
0
+
Passed
+
+
+
0
+
Failed
+
+
+
0
+
Flaky
+
+
+
0
+
Skipped
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0ms - 0ms
+
+
+
+
+
+
+
+
+ 0ms
+ 0ms
+
+
+
+
+
+ 0 tests
+
+
+
+
+
+
+
+
+
+
+ Page 1 of 1
+
+
+
+
+
+
No tests match your filters
+
Try adjusting your search criteria
+
+
+
+
+
+
+
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/types.go b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/types.go
index 3b51674f4..00d2d9d66 100644
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/types.go
+++ b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/types.go
@@ -88,4 +88,7 @@ type Image struct {
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/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/output.go b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/output.go
index 24f49f638..af62bbf13 100644
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/output.go
+++ b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/output.go
@@ -2,10 +2,11 @@ package flags
import (
"encoding/json"
+ "errors"
+ "fmt"
"reflect"
"strings"
- "github.com/pkg/errors"
"github.com/spf13/pflag"
)
@@ -90,6 +91,6 @@ func (o *OutputFlags) Marshal(v interface{}) ([]byte, error) {
}
return nil, errors.New("names format requires an array of structs")
default:
- return nil, errors.Errorf("invalid output format: %s", o.Output)
+ return nil, fmt.Errorf("invalid output format: %s", o.Output)
}
}
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/logging.go b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/logging.go
index 0b84ca41c..1cf299a7c 100644
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/logging.go
+++ b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/logging.go
@@ -17,5 +17,9 @@ func GinkgoLogrFunc(writer ginkgo.GinkgoWriterInterface) logr.Logger {
} else {
writer.Printf("%s %s\n", prefix, args)
}
- }, funcr.Options{})
+ }, 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/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/parallel.go b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/parallel.go
index 890cebb09..b7f95838a 100644
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/parallel.go
+++ b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/parallel.go
@@ -25,7 +25,7 @@ func SpawnProcessToRunTest(ctx context.Context, testName string, timeout time.Du
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
- command := exec.CommandContext(longerCtx, os.Args[0], "run-test", "--output=json", testName)
+ command := exec.CommandContext(longerCtx, os.Args[0], "run-test", "--output=json", fmt.Sprintf("--timeout=%s", timeout), testName)
command.Stdout = stdout
command.Stderr = stderr
@@ -37,30 +37,22 @@ func SpawnProcessToRunTest(ctx context.Context, testName string, timeout time.Du
}
go func() {
+ // interrupt after timeout, or exit early if the process finishes first
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)
- }
+ }
+ if command.Process != nil {
+ _ = command.Process.Signal(syscall.SIGINT)
+ }
+ // Canceled means the process exited and the context was cancelled — no need to escalate
+ if timeoutCtx.Err() == context.Canceled {
+ return
+ }
+ // if the process is hung, send SIGABRT after a grace period for a stack dump
+ <-time.After(time.Minute)
+ if command.Process != nil {
+ _ = command.Process.Signal(syscall.SIGABRT)
}
}()
@@ -74,7 +66,7 @@ func SpawnProcessToRunTest(ctx context.Context, testName string, timeout time.Du
}
fmt.Fprintf(stderr, "Command Error: %v\n", cmdErr)
- fmt.Fprintf(stderr, "Deserializaion Error: %v\n", parseErr)
+ fmt.Fprintf(stderr, "Deserialization Error: %v\n", parseErr)
return newTestResult(testName, result, start, time.Now(), stdout, stderr)
}
@@ -83,28 +75,61 @@ func newTestResultFromOutput(stdout *bytes.Buffer) (*extensiontests.ExtensionTes
return nil, errors.New("no output from command")
}
+ jsonData, err := extractJSON(stdout.Bytes())
+ if err != nil {
+ return nil, err
+ }
+
// 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 arrayItemErr := json.Unmarshal(jsonData, &retArray); arrayItemErr == nil {
if len(retArray) != 1 {
- return nil, errors.New("expected 1 result, got %v results")
+ return nil, fmt.Errorf("expected 1 result, got %d results", len(retArray))
}
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 {
+ if singleItemErr := json.Unmarshal(jsonData, ret); singleItemErr != nil {
return nil, singleItemErr
}
return ret, nil
}
+// extractJSON finds the first JSON object or array in output, skipping any non-JSON
+// lines that precede it (e.g. klog lines, Ginkgo reporter output). It also ignores
+// trailing non-JSON content after the JSON payload. This is necessary because extension
+// binaries may emit log output to stdout before or after the JSON result, which would
+// otherwise cause deserialization failures.
+func extractJSON(output []byte) ([]byte, error) {
+ lines := bytes.Split(output, []byte("\n"))
+ for i, line := range lines {
+ trimmed := bytes.TrimSpace(line)
+ if len(trimmed) > 0 && (trimmed[0] == '{' || trimmed[0] == '[') {
+ // Calculate byte offset to the start of the JSON content
+ offset := 0
+ for j := 0; j < i; j++ {
+ offset += len(lines[j]) + 1 // +1 for the newline
+ }
+
+ var raw json.RawMessage
+ dec := json.NewDecoder(bytes.NewReader(output[offset:]))
+ if err := dec.Decode(&raw); err != nil {
+ continue // not valid JSON, try next candidate line
+ }
+ return raw, nil
+ }
+ }
+
+ return nil, fmt.Errorf("no JSON object or array found in output (%d bytes)", len(output))
+}
+
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)
+ dbEnd := dbtime.DBTime(end)
ret := &extensiontests.ExtensionTestResult{
Name: name,
Lifecycle: "", // lifecycle is completed one level above this.
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/util.go b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/util.go
index 308576c63..90c5c2bd1 100644
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/util.go
+++ b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/util.go
@@ -11,7 +11,6 @@ import (
"github.com/onsi/ginkgo/v2"
"github.com/onsi/ginkgo/v2/types"
"github.com/onsi/gomega"
- "github.com/pkg/errors"
"github.com/openshift-eng/openshift-tests-extension/pkg/util/sets"
@@ -21,7 +20,7 @@ import (
func configureGinkgo() (*types.SuiteConfig, *types.ReporterConfig, error) {
if !ginkgo.GetSuite().InPhaseBuildTree() {
if err := ginkgo.GetSuite().BuildTree(); err != nil {
- return nil, nil, errors.Wrapf(err, "couldn't build ginkgo tree")
+ return nil, nil, fmt.Errorf("couldn't build ginkgo tree: %w", err)
}
}
@@ -57,7 +56,7 @@ func BuildExtensionTestSpecsFromOpenShiftGinkgoSuite(selectFns ...ext.SelectFunc
cwd, err := os.Getwd()
if err != nil {
- return nil, errors.Wrap(err, "couldn't get current working directory")
+ return nil, fmt.Errorf("couldn't get current working directory: %w", err)
}
ginkgo.GetSuite().WalkTests(func(name string, spec types.TestSpec) {
@@ -81,22 +80,17 @@ func BuildExtensionTestSpecsFromOpenShiftGinkgoSuite(selectFns ...ext.SelectFunc
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
- }
- }
+ summary := findSpecReport(ginkgo.GetSuite().GetReport().SpecReports)
result.Output = summary.CapturedGinkgoWriterOutput
result.Error = summary.CapturedStdOutErr
- switch {
- case summary.State == types.SpecStatePassed:
+ switch summary.State {
+ case types.SpecStatePassed:
result.Result = ext.ResultPassed
- case summary.State == types.SpecStateSkipped, summary.State == types.SpecStatePending:
+ case types.SpecStateSkipped, types.SpecStatePending:
result.Result = ext.ResultSkipped
if len(summary.Failure.Message) > 0 {
result.Output = fmt.Sprintf(
@@ -115,7 +109,7 @@ func BuildExtensionTestSpecsFromOpenShiftGinkgoSuite(selectFns ...ext.SelectFunc
summary.Failure.ForwardedPanic,
)
}
- case summary.State == types.SpecStateFailed, summary.State == types.SpecStatePanicked, summary.State == types.SpecStateInterrupted:
+ case types.SpecStateFailed, types.SpecStatePanicked, types.SpecStateInterrupted, types.SpecStateAborted:
result.Result = ext.ResultFailed
var errors []string
if len(summary.Failure.ForwardedPanic) > 0 {
@@ -126,16 +120,34 @@ func BuildExtensionTestSpecsFromOpenShiftGinkgoSuite(selectFns ...ext.SelectFunc
}
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 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")
+ case types.SpecStateInvalid:
+ result.Result = ext.ResultFailed
+ result.Error = fmt.Sprintf("test produced no spec report; this is a bug in the test framework: %#v", summary)
default:
- panic(fmt.Sprintf("test produced unknown outcome: %#v", summary))
+ result.Result = ext.ResultFailed
+ result.Error = fmt.Sprintf("test produced unknown outcome: %#v", summary)
}
return result
},
- RunParallel: func(ctx context.Context) *ext.ExtensionTestResult {
- // TODO pass through timeout and determine Lifecycle
- return SpawnProcessToRunTest(ctx, name, 90*time.Minute)
- },
+ }
+ 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)
})
@@ -198,3 +210,40 @@ func lastFilenameSegment(filename string) string {
}
return filename
}
+
+// findSpecReport selects the best matching spec report from the list of reports
+// produced by RunSpec. It first looks for a report that was actually attempted
+// (NumAttempts > 0), which covers passed/failed/panicked specs. If none is found
+// (as happens with Pending or Skipped specs where Ginkgo never enters the
+// execution loop), it falls back to the last report in the list.
+func findSpecReport(reports types.SpecReports) types.SpecReport {
+ var summary types.SpecReport
+ for _, report := range reports {
+ if report.NumAttempts > 0 {
+ summary = report
+ }
+ }
+ // Pending/Skipped specs have NumAttempts==0; fall back to the last report
+ if summary.State == types.SpecStateInvalid && len(reports) > 0 {
+ summary = reports[len(reports)-1]
+ }
+ return summary
+}
+
+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/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/.gitignore b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/.gitignore
deleted file mode 100644
index daf913b1b..000000000
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/.gitignore
+++ /dev/null
@@ -1,24 +0,0 @@
-# Compiled Object files, Static and Dynamic libs (Shared Objects)
-*.o
-*.a
-*.so
-
-# Folders
-_obj
-_test
-
-# Architecture specific extensions/prefixes
-*.[568vq]
-[568vq].out
-
-*.cgo1.go
-*.cgo2.c
-_cgo_defun.c
-_cgo_gotypes.go
-_cgo_export.*
-
-_testmain.go
-
-*.exe
-*.test
-*.prof
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/.travis.yml b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/.travis.yml
deleted file mode 100644
index 9159de03e..000000000
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/.travis.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-language: go
-go_import_path: github.com/pkg/errors
-go:
- - 1.11.x
- - 1.12.x
- - 1.13.x
- - tip
-
-script:
- - make check
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/LICENSE b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/LICENSE
deleted file mode 100644
index 835ba3e75..000000000
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/LICENSE
+++ /dev/null
@@ -1,23 +0,0 @@
-Copyright (c) 2015, Dave Cheney
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/Makefile b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/Makefile
deleted file mode 100644
index ce9d7cded..000000000
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/Makefile
+++ /dev/null
@@ -1,44 +0,0 @@
-PKGS := github.com/pkg/errors
-SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS))
-GO := go
-
-check: test vet gofmt misspell unconvert staticcheck ineffassign unparam
-
-test:
- $(GO) test $(PKGS)
-
-vet: | test
- $(GO) vet $(PKGS)
-
-staticcheck:
- $(GO) get honnef.co/go/tools/cmd/staticcheck
- staticcheck -checks all $(PKGS)
-
-misspell:
- $(GO) get github.com/client9/misspell/cmd/misspell
- misspell \
- -locale GB \
- -error \
- *.md *.go
-
-unconvert:
- $(GO) get github.com/mdempsky/unconvert
- unconvert -v $(PKGS)
-
-ineffassign:
- $(GO) get github.com/gordonklaus/ineffassign
- find $(SRCDIRS) -name '*.go' | xargs ineffassign
-
-pedantic: check errcheck
-
-unparam:
- $(GO) get mvdan.cc/unparam
- unparam ./...
-
-errcheck:
- $(GO) get github.com/kisielk/errcheck
- errcheck $(PKGS)
-
-gofmt:
- @echo Checking code is gofmted
- @test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)"
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/README.md b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/README.md
deleted file mode 100644
index 54dfdcb12..000000000
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/README.md
+++ /dev/null
@@ -1,59 +0,0 @@
-# errors [](https://travis-ci.org/pkg/errors) [](https://ci.appveyor.com/project/davecheney/errors/branch/master) [](http://godoc.org/github.com/pkg/errors) [](https://goreportcard.com/report/github.com/pkg/errors) [](https://sourcegraph.com/github.com/pkg/errors?badge)
-
-Package errors provides simple error handling primitives.
-
-`go get github.com/pkg/errors`
-
-The traditional error handling idiom in Go is roughly akin to
-```go
-if err != nil {
- return err
-}
-```
-which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
-
-## Adding context to an error
-
-The errors.Wrap function returns a new error that adds context to the original error. For example
-```go
-_, err := ioutil.ReadAll(r)
-if err != nil {
- return errors.Wrap(err, "read failed")
-}
-```
-## Retrieving the cause of an error
-
-Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
-```go
-type causer interface {
- Cause() error
-}
-```
-`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
-```go
-switch err := errors.Cause(err).(type) {
-case *MyError:
- // handle specifically
-default:
- // unknown error
-}
-```
-
-[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
-
-## Roadmap
-
-With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows:
-
-- 0.9. Remove pre Go 1.9 and Go 1.10 support, address outstanding pull requests (if possible)
-- 1.0. Final release.
-
-## Contributing
-
-Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports.
-
-Before sending a PR, please discuss your change by raising an issue.
-
-## License
-
-BSD-2-Clause
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/appveyor.yml b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/appveyor.yml
deleted file mode 100644
index a932eade0..000000000
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/appveyor.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-version: build-{build}.{branch}
-
-clone_folder: C:\gopath\src\github.com\pkg\errors
-shallow_clone: true # for startup speed
-
-environment:
- GOPATH: C:\gopath
-
-platform:
- - x64
-
-# http://www.appveyor.com/docs/installed-software
-install:
- # some helpful output for debugging builds
- - go version
- - go env
- # pre-installed MinGW at C:\MinGW is 32bit only
- # but MSYS2 at C:\msys64 has mingw64
- - set PATH=C:\msys64\mingw64\bin;%PATH%
- - gcc --version
- - g++ --version
-
-build_script:
- - go install -v ./...
-
-test_script:
- - set PATH=C:\gopath\bin;%PATH%
- - go test -v ./...
-
-#artifacts:
-# - path: '%GOPATH%\bin\*.exe'
-deploy: off
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/errors.go b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/errors.go
deleted file mode 100644
index 161aea258..000000000
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/errors.go
+++ /dev/null
@@ -1,288 +0,0 @@
-// Package errors provides simple error handling primitives.
-//
-// The traditional error handling idiom in Go is roughly akin to
-//
-// if err != nil {
-// return err
-// }
-//
-// which when applied recursively up the call stack results in error reports
-// without context or debugging information. The errors package allows
-// programmers to add context to the failure path in their code in a way
-// that does not destroy the original value of the error.
-//
-// Adding context to an error
-//
-// The errors.Wrap function returns a new error that adds context to the
-// original error by recording a stack trace at the point Wrap is called,
-// together with the supplied message. For example
-//
-// _, err := ioutil.ReadAll(r)
-// if err != nil {
-// return errors.Wrap(err, "read failed")
-// }
-//
-// If additional control is required, the errors.WithStack and
-// errors.WithMessage functions destructure errors.Wrap into its component
-// operations: annotating an error with a stack trace and with a message,
-// respectively.
-//
-// Retrieving the cause of an error
-//
-// Using errors.Wrap constructs a stack of errors, adding context to the
-// preceding error. Depending on the nature of the error it may be necessary
-// to reverse the operation of errors.Wrap to retrieve the original error
-// for inspection. Any error value which implements this interface
-//
-// type causer interface {
-// Cause() error
-// }
-//
-// can be inspected by errors.Cause. errors.Cause will recursively retrieve
-// the topmost error that does not implement causer, which is assumed to be
-// the original cause. For example:
-//
-// switch err := errors.Cause(err).(type) {
-// case *MyError:
-// // handle specifically
-// default:
-// // unknown error
-// }
-//
-// Although the causer interface is not exported by this package, it is
-// considered a part of its stable public interface.
-//
-// Formatted printing of errors
-//
-// All error values returned from this package implement fmt.Formatter and can
-// be formatted by the fmt package. The following verbs are supported:
-//
-// %s print the error. If the error has a Cause it will be
-// printed recursively.
-// %v see %s
-// %+v extended format. Each Frame of the error's StackTrace will
-// be printed in detail.
-//
-// Retrieving the stack trace of an error or wrapper
-//
-// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
-// invoked. This information can be retrieved with the following interface:
-//
-// type stackTracer interface {
-// StackTrace() errors.StackTrace
-// }
-//
-// The returned errors.StackTrace type is defined as
-//
-// type StackTrace []Frame
-//
-// The Frame type represents a call site in the stack trace. Frame supports
-// the fmt.Formatter interface that can be used for printing information about
-// the stack trace of this error. For example:
-//
-// if err, ok := err.(stackTracer); ok {
-// for _, f := range err.StackTrace() {
-// fmt.Printf("%+s:%d\n", f, f)
-// }
-// }
-//
-// Although the stackTracer interface is not exported by this package, it is
-// considered a part of its stable public interface.
-//
-// See the documentation for Frame.Format for more details.
-package errors
-
-import (
- "fmt"
- "io"
-)
-
-// New returns an error with the supplied message.
-// New also records the stack trace at the point it was called.
-func New(message string) error {
- return &fundamental{
- msg: message,
- stack: callers(),
- }
-}
-
-// Errorf formats according to a format specifier and returns the string
-// as a value that satisfies error.
-// Errorf also records the stack trace at the point it was called.
-func Errorf(format string, args ...interface{}) error {
- return &fundamental{
- msg: fmt.Sprintf(format, args...),
- stack: callers(),
- }
-}
-
-// fundamental is an error that has a message and a stack, but no caller.
-type fundamental struct {
- msg string
- *stack
-}
-
-func (f *fundamental) Error() string { return f.msg }
-
-func (f *fundamental) Format(s fmt.State, verb rune) {
- switch verb {
- case 'v':
- if s.Flag('+') {
- io.WriteString(s, f.msg)
- f.stack.Format(s, verb)
- return
- }
- fallthrough
- case 's':
- io.WriteString(s, f.msg)
- case 'q':
- fmt.Fprintf(s, "%q", f.msg)
- }
-}
-
-// WithStack annotates err with a stack trace at the point WithStack was called.
-// If err is nil, WithStack returns nil.
-func WithStack(err error) error {
- if err == nil {
- return nil
- }
- return &withStack{
- err,
- callers(),
- }
-}
-
-type withStack struct {
- error
- *stack
-}
-
-func (w *withStack) Cause() error { return w.error }
-
-// Unwrap provides compatibility for Go 1.13 error chains.
-func (w *withStack) Unwrap() error { return w.error }
-
-func (w *withStack) Format(s fmt.State, verb rune) {
- switch verb {
- case 'v':
- if s.Flag('+') {
- fmt.Fprintf(s, "%+v", w.Cause())
- w.stack.Format(s, verb)
- return
- }
- fallthrough
- case 's':
- io.WriteString(s, w.Error())
- case 'q':
- fmt.Fprintf(s, "%q", w.Error())
- }
-}
-
-// Wrap returns an error annotating err with a stack trace
-// at the point Wrap is called, and the supplied message.
-// If err is nil, Wrap returns nil.
-func Wrap(err error, message string) error {
- if err == nil {
- return nil
- }
- err = &withMessage{
- cause: err,
- msg: message,
- }
- return &withStack{
- err,
- callers(),
- }
-}
-
-// Wrapf returns an error annotating err with a stack trace
-// at the point Wrapf is called, and the format specifier.
-// If err is nil, Wrapf returns nil.
-func Wrapf(err error, format string, args ...interface{}) error {
- if err == nil {
- return nil
- }
- err = &withMessage{
- cause: err,
- msg: fmt.Sprintf(format, args...),
- }
- return &withStack{
- err,
- callers(),
- }
-}
-
-// WithMessage annotates err with a new message.
-// If err is nil, WithMessage returns nil.
-func WithMessage(err error, message string) error {
- if err == nil {
- return nil
- }
- return &withMessage{
- cause: err,
- msg: message,
- }
-}
-
-// WithMessagef annotates err with the format specifier.
-// If err is nil, WithMessagef returns nil.
-func WithMessagef(err error, format string, args ...interface{}) error {
- if err == nil {
- return nil
- }
- return &withMessage{
- cause: err,
- msg: fmt.Sprintf(format, args...),
- }
-}
-
-type withMessage struct {
- cause error
- msg string
-}
-
-func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
-func (w *withMessage) Cause() error { return w.cause }
-
-// Unwrap provides compatibility for Go 1.13 error chains.
-func (w *withMessage) Unwrap() error { return w.cause }
-
-func (w *withMessage) Format(s fmt.State, verb rune) {
- switch verb {
- case 'v':
- if s.Flag('+') {
- fmt.Fprintf(s, "%+v\n", w.Cause())
- io.WriteString(s, w.msg)
- return
- }
- fallthrough
- case 's', 'q':
- io.WriteString(s, w.Error())
- }
-}
-
-// Cause returns the underlying cause of the error, if possible.
-// An error value has a cause if it implements the following
-// interface:
-//
-// type causer interface {
-// Cause() error
-// }
-//
-// If the error does not implement Cause, the original error will
-// be returned. If the error is nil, nil will be returned without further
-// investigation.
-func Cause(err error) error {
- type causer interface {
- Cause() error
- }
-
- for err != nil {
- cause, ok := err.(causer)
- if !ok {
- break
- }
- err = cause.Cause()
- }
- return err
-}
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/go113.go b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/go113.go
deleted file mode 100644
index be0d10d0c..000000000
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/go113.go
+++ /dev/null
@@ -1,38 +0,0 @@
-// +build go1.13
-
-package errors
-
-import (
- stderrors "errors"
-)
-
-// Is reports whether any error in err's chain matches target.
-//
-// The chain consists of err itself followed by the sequence of errors obtained by
-// repeatedly calling Unwrap.
-//
-// An error is considered to match a target if it is equal to that target or if
-// it implements a method Is(error) bool such that Is(target) returns true.
-func Is(err, target error) bool { return stderrors.Is(err, target) }
-
-// As finds the first error in err's chain that matches target, and if so, sets
-// target to that error value and returns true.
-//
-// The chain consists of err itself followed by the sequence of errors obtained by
-// repeatedly calling Unwrap.
-//
-// An error matches target if the error's concrete value is assignable to the value
-// pointed to by target, or if the error has a method As(interface{}) bool such that
-// As(target) returns true. In the latter case, the As method is responsible for
-// setting target.
-//
-// As will panic if target is not a non-nil pointer to either a type that implements
-// error, or to any interface type. As returns false if err is nil.
-func As(err error, target interface{}) bool { return stderrors.As(err, target) }
-
-// Unwrap returns the result of calling the Unwrap method on err, if err's
-// type contains an Unwrap method returning error.
-// Otherwise, Unwrap returns nil.
-func Unwrap(err error) error {
- return stderrors.Unwrap(err)
-}
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/stack.go b/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/stack.go
deleted file mode 100644
index 779a8348f..000000000
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/github.com/pkg/errors/stack.go
+++ /dev/null
@@ -1,177 +0,0 @@
-package errors
-
-import (
- "fmt"
- "io"
- "path"
- "runtime"
- "strconv"
- "strings"
-)
-
-// Frame represents a program counter inside a stack frame.
-// For historical reasons if Frame is interpreted as a uintptr
-// its value represents the program counter + 1.
-type Frame uintptr
-
-// pc returns the program counter for this frame;
-// multiple frames may have the same PC value.
-func (f Frame) pc() uintptr { return uintptr(f) - 1 }
-
-// file returns the full path to the file that contains the
-// function for this Frame's pc.
-func (f Frame) file() string {
- fn := runtime.FuncForPC(f.pc())
- if fn == nil {
- return "unknown"
- }
- file, _ := fn.FileLine(f.pc())
- return file
-}
-
-// line returns the line number of source code of the
-// function for this Frame's pc.
-func (f Frame) line() int {
- fn := runtime.FuncForPC(f.pc())
- if fn == nil {
- return 0
- }
- _, line := fn.FileLine(f.pc())
- return line
-}
-
-// name returns the name of this function, if known.
-func (f Frame) name() string {
- fn := runtime.FuncForPC(f.pc())
- if fn == nil {
- return "unknown"
- }
- return fn.Name()
-}
-
-// Format formats the frame according to the fmt.Formatter interface.
-//
-// %s source file
-// %d source line
-// %n function name
-// %v equivalent to %s:%d
-//
-// Format accepts flags that alter the printing of some verbs, as follows:
-//
-// %+s function name and path of source file relative to the compile time
-// GOPATH separated by \n\t (\n\t)
-// %+v equivalent to %+s:%d
-func (f Frame) Format(s fmt.State, verb rune) {
- switch verb {
- case 's':
- switch {
- case s.Flag('+'):
- io.WriteString(s, f.name())
- io.WriteString(s, "\n\t")
- io.WriteString(s, f.file())
- default:
- io.WriteString(s, path.Base(f.file()))
- }
- case 'd':
- io.WriteString(s, strconv.Itoa(f.line()))
- case 'n':
- io.WriteString(s, funcname(f.name()))
- case 'v':
- f.Format(s, 's')
- io.WriteString(s, ":")
- f.Format(s, 'd')
- }
-}
-
-// MarshalText formats a stacktrace Frame as a text string. The output is the
-// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs.
-func (f Frame) MarshalText() ([]byte, error) {
- name := f.name()
- if name == "unknown" {
- return []byte(name), nil
- }
- return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil
-}
-
-// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
-type StackTrace []Frame
-
-// Format formats the stack of Frames according to the fmt.Formatter interface.
-//
-// %s lists source files for each Frame in the stack
-// %v lists the source file and line number for each Frame in the stack
-//
-// Format accepts flags that alter the printing of some verbs, as follows:
-//
-// %+v Prints filename, function, and line number for each Frame in the stack.
-func (st StackTrace) Format(s fmt.State, verb rune) {
- switch verb {
- case 'v':
- switch {
- case s.Flag('+'):
- for _, f := range st {
- io.WriteString(s, "\n")
- f.Format(s, verb)
- }
- case s.Flag('#'):
- fmt.Fprintf(s, "%#v", []Frame(st))
- default:
- st.formatSlice(s, verb)
- }
- case 's':
- st.formatSlice(s, verb)
- }
-}
-
-// formatSlice will format this StackTrace into the given buffer as a slice of
-// Frame, only valid when called with '%s' or '%v'.
-func (st StackTrace) formatSlice(s fmt.State, verb rune) {
- io.WriteString(s, "[")
- for i, f := range st {
- if i > 0 {
- io.WriteString(s, " ")
- }
- f.Format(s, verb)
- }
- io.WriteString(s, "]")
-}
-
-// stack represents a stack of program counters.
-type stack []uintptr
-
-func (s *stack) Format(st fmt.State, verb rune) {
- switch verb {
- case 'v':
- switch {
- case st.Flag('+'):
- for _, pc := range *s {
- f := Frame(pc)
- fmt.Fprintf(st, "\n%+v", f)
- }
- }
- }
-}
-
-func (s *stack) StackTrace() StackTrace {
- f := make([]Frame, len(*s))
- for i := 0; i < len(f); i++ {
- f[i] = Frame((*s)[i])
- }
- return f
-}
-
-func callers() *stack {
- const depth = 32
- var pcs [depth]uintptr
- n := runtime.Callers(3, pcs[:])
- var st stack = pcs[0:n]
- return &st
-}
-
-// funcname removes the path prefix component of a function's name reported by func.Name().
-func funcname(name string) string {
- i := strings.LastIndex(name, "/")
- name = name[i+1:]
- i = strings.Index(name, ".")
- return name[i+1:]
-}
diff --git a/cmd/cloud-controller-manager-aws-tests-ext/vendor/modules.txt b/cmd/cloud-controller-manager-aws-tests-ext/vendor/modules.txt
index ab8df0ca2..b689947ae 100644
--- a/cmd/cloud-controller-manager-aws-tests-ext/vendor/modules.txt
+++ b/cmd/cloud-controller-manager-aws-tests-ext/vendor/modules.txt
@@ -288,7 +288,7 @@ github.com/onsi/gomega/types
# github.com/opencontainers/go-digest v1.0.0
## explicit; go 1.13
github.com/opencontainers/go-digest
-# github.com/openshift-eng/openshift-tests-extension v0.0.0-20250916161632-d81c09058835
+# github.com/openshift-eng/openshift-tests-extension v0.0.0-20260626105913-1f81f3df939a
## explicit; go 1.23.0
github.com/openshift-eng/openshift-tests-extension/pkg/cmd
github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdimages
@@ -315,9 +315,6 @@ github.com/openshift/client-go/config/applyconfigurations/config/v1
github.com/openshift/client-go/config/applyconfigurations/internal
github.com/openshift/client-go/config/clientset/versioned/scheme
github.com/openshift/client-go/config/clientset/versioned/typed/config/v1
-# github.com/pkg/errors v0.9.1
-## explicit
-github.com/pkg/errors
# github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
## explicit
github.com/pmezard/go-difflib/difflib