From c11f8a541d81d33d829719b67bbb73c09f5bbf93 Mon Sep 17 00:00:00 2001 From: Bryan Cox Date: Tue, 22 Sep 2026 14:28:44 -0400 Subject: [PATCH 1/8] test: consolidate split unit test scenarios Move existing error, real-world credential, and unsupported log-level scenarios into their canonical top-level test functions. This demonstrates the structure required by TESTING.md without changing test behavior. Signed-off-by: Bryan Cox Commit-Message-Assisted-by: Claude (via Claude Code) --- cmd/cluster/core/destroy_test.go | 70 ++++++++++++++++---------------- cmd/infra/aws/util/sts_test.go | 67 ++++++++++-------------------- support/util/loglevel_test.go | 43 ++++++++------------ 3 files changed, 73 insertions(+), 107 deletions(-) diff --git a/cmd/cluster/core/destroy_test.go b/cmd/cluster/core/destroy_test.go index d6798c4aa785..ab8684abbd49 100644 --- a/cmd/cluster/core/destroy_test.go +++ b/cmd/cluster/core/destroy_test.go @@ -367,6 +367,40 @@ func TestForceRemoveAllFinalizers(t *testing.T) { g.Expect(err).ToNot(HaveOccurred()) g.Expect(updatedHC.Finalizers).To(Equal([]string{destroyFinalizer})) }) + + t.Run("When client operations fail, it should return an aggregated error", func(t *testing.T) { + g := NewGomegaWithT(t) + ctx := context.Background() + + hc := &hyperv1.HostedCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: "clusters", + Finalizers: []string{destroyFinalizer, "hypershift.openshift.io/finalizer"}, + }, + } + + c := fake.NewClientBuilder(). + WithScheme(hyperapi.Scheme). + WithObjects(hc). + WithInterceptorFuncs(interceptor.Funcs{ + Patch: func(_ context.Context, _ client.WithWatch, _ client.Object, _ client.Patch, _ ...client.PatchOption) error { + return fmt.Errorf("API server unavailable") + }, + }). + Build() + + opts := &DestroyOptions{ + Name: "test-cluster", + Namespace: "clusters", + Log: log.Log, + } + + err := forceRemoveAllFinalizers(ctx, hc, opts, c) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("force removal encountered")) + g.Expect(err.Error()).To(ContainSubstring("API server unavailable")) + }) } func TestStripFinalizers(t *testing.T) { @@ -503,42 +537,6 @@ func TestStripNodePoolFinalizers(t *testing.T) { }) } -func TestForceRemoveAllFinalizersErrors(t *testing.T) { - t.Run("When client operations fail, it should return an aggregated error", func(t *testing.T) { - g := NewGomegaWithT(t) - ctx := context.Background() - - hc := &hyperv1.HostedCluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-cluster", - Namespace: "clusters", - Finalizers: []string{destroyFinalizer, "hypershift.openshift.io/finalizer"}, - }, - } - - c := fake.NewClientBuilder(). - WithScheme(hyperapi.Scheme). - WithObjects(hc). - WithInterceptorFuncs(interceptor.Funcs{ - Patch: func(_ context.Context, _ client.WithWatch, _ client.Object, _ client.Patch, _ ...client.PatchOption) error { - return fmt.Errorf("API server unavailable") - }, - }). - Build() - - opts := &DestroyOptions{ - Name: "test-cluster", - Namespace: "clusters", - Log: log.Log, - } - - err := forceRemoveAllFinalizers(ctx, hc, opts, c) - g.Expect(err).To(HaveOccurred()) - g.Expect(err.Error()).To(ContainSubstring("force removal encountered")) - g.Expect(err.Error()).To(ContainSubstring("API server unavailable")) - }) -} - func TestGetCluster(t *testing.T) { t.Run("When an injected client contains a hosted cluster, it should return that cluster", func(t *testing.T) { g := NewGomegaWithT(t) diff --git a/cmd/infra/aws/util/sts_test.go b/cmd/infra/aws/util/sts_test.go index ed58709af79a..e48878cbe689 100644 --- a/cmd/infra/aws/util/sts_test.go +++ b/cmd/infra/aws/util/sts_test.go @@ -242,6 +242,28 @@ func TestParseSTSCredentialsFile(t *testing.T) { } }, }, + { + name: "When given real-world STS credentials, it should preserve all credential fields", + fileContent: `{ + "Credentials": { + "AccessKeyId": "ASIAIOSFODNN7EXAMPLE", + "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY", + "SessionToken": "FwoGZXIvYXdzEBYaDHExampleToken1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", + "Expiration": "2025-01-30T12:00:00Z" + } +}`, + validateCreds: func(t *testing.T, creds *aws.Credentials) { + if creds.AccessKeyID != "ASIAIOSFODNN7EXAMPLE" { + t.Errorf("Expected AccessKeyID ASIAIOSFODNN7EXAMPLE, got %s", creds.AccessKeyID) + } + if creds.SecretAccessKey == "" { + t.Error("Expected non-empty SecretAccessKey") + } + if creds.SessionToken == "" { + t.Error("Expected non-empty SessionToken") + } + }, + }, } for _, tc := range testCases { @@ -295,48 +317,3 @@ func TestParseSTSCredentialsFile(t *testing.T) { }) } } - -func TestParseSTSCredentialsFile_RealWorldFormat(t *testing.T) { - realWorldJSON := `{ - "Credentials": { - "AccessKeyId": "ASIAIOSFODNN7EXAMPLE", - "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY", - "SessionToken": "FwoGZXIvYXdzEBYaDHExampleToken1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", - "Expiration": "2025-01-30T12:00:00Z" - } -}` - - tmpFile, err := os.CreateTemp(t.TempDir(), "real-world-sts-*.json") - if err != nil { - t.Fatalf("Failed to create temp file: %v", err) - } - defer tmpFile.Close() - - if _, err := tmpFile.WriteString(realWorldJSON); err != nil { - t.Fatalf("Failed to write to temp file: %v", err) - } - - creds, err := ParseSTSCredentialsFile(tmpFile.Name()) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - return - } - - if creds == nil { - t.Fatal("Expected non-nil credentials") - return - } - - expectedAccessKey := "ASIAIOSFODNN7EXAMPLE" - if creds.AccessKeyID != expectedAccessKey { - t.Errorf("Expected AccessKeyID %s, got %s", expectedAccessKey, creds.AccessKeyID) - } - - if creds.SecretAccessKey == "" { - t.Error("Expected non-empty SecretAccessKey") - } - - if creds.SessionToken == "" { - t.Error("Expected non-empty SessionToken") - } -} diff --git a/support/util/loglevel_test.go b/support/util/loglevel_test.go index be855b0108ec..fb56054afd52 100644 --- a/support/util/loglevel_test.go +++ b/support/util/loglevel_test.go @@ -51,9 +51,10 @@ func TestLogLevelToKlogVerbosity(t *testing.T) { func TestLogLevelToEtcdLevel(t *testing.T) { tests := []struct { - name string - level hyperv1.LogLevel - expected string + name string + level hyperv1.LogLevel + expected string + expectPanic bool }{ { name: "When LogLevel is empty, it should return etcd level info", @@ -70,38 +71,28 @@ func TestLogLevelToEtcdLevel(t *testing.T) { level: hyperv1.Debug, expected: "debug", }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - g := NewWithT(t) - g.Expect(LogLevelToEtcdLevel(tt.level)).To(Equal(tt.expected)) - }) - } -} - -func TestLogLevelToEtcdLevelPanicsOnUnsupported(t *testing.T) { - // Trace and TraceAll are valid LogLevel values but are not supported by etcd - // and are rejected at the API level (CEL). If one ever reaches this mapping - // (e.g. via a non-CRD path), it must panic rather than silently degrade. - tests := []struct { - name string - level hyperv1.LogLevel - }{ { - name: "When LogLevel is Trace, it should panic", - level: hyperv1.Trace, + name: "When LogLevel is Trace, it should panic", + level: hyperv1.Trace, + expectPanic: true, }, { - name: "When LogLevel is TraceAll, it should panic", - level: hyperv1.TraceAll, + name: "When LogLevel is TraceAll, it should panic", + level: hyperv1.TraceAll, + expectPanic: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - g.Expect(func() { LogLevelToEtcdLevel(tt.level) }).To(Panic()) + if tt.expectPanic { + // Trace levels are rejected by etcd's API-level validation. Panic if + // one reaches this mapping through a non-CRD path. + g.Expect(func() { LogLevelToEtcdLevel(tt.level) }).To(Panic()) + return + } + g.Expect(LogLevelToEtcdLevel(tt.level)).To(Equal(tt.expected)) }) } } From 4eace3e8931f455a20ac3f406622f34645c5d05c Mon Sep 17 00:00:00 2001 From: Bryan Cox Date: Tue, 22 Sep 2026 14:30:42 -0400 Subject: [PATCH 2/8] feat(lint): add unit test structure analyzer Add type-aware mapping for package functions and methods, including external tests and method aliases. Use conservative call-based disambiguation when a test name does not identify one production symbol. Cover split, ambiguous, generated, exception, and integration-style fixtures. Register the analyzer without enabling production lint yet. Signed-off-by: Bryan Cox Commit-Message-Assisted-by: Claude (via Claude Code) --- .../analyzers/testfuncstructure/legacy.go | 5 + .../testdata/src/a/ambiguous/production.go | 9 + .../src/a/ambiguous/production_test.go | 23 + .../testdata/src/a/external/external.go | 17 + .../testdata/src/a/external/external_test.go | 30 + .../testdata/src/a/good/generated.go | 5 + .../testdata/src/a/good/generated_test.go | 9 + .../testdata/src/a/good/production.go | 25 + .../testdata/src/a/good/production_test.go | 99 +++ .../testdata/src/a/legacy/production.go | 3 + .../testdata/src/a/legacy/production_test.go | 11 + .../src/a/legacyambiguous/production.go | 5 + .../src/a/legacyambiguous/production_test.go | 12 + .../testdata/src/a/precedence/production.go | 7 + .../src/a/precedence/production_test.go | 6 + .../testdata/src/a/scenarioonly/production.go | 3 + .../src/a/scenarioonly/production_test.go | 11 + .../testdata/src/a/split/production.go | 9 + .../testdata/src/a/split/production_test.go | 25 + .../testdata/src/test/e2e/good/production.go | 3 + .../src/test/e2e/good/production_test.go | 7 + .../src/test/envtest/good/production.go | 3 + .../src/test/envtest/good/production_test.go | 7 + .../src/test/integration/good/production.go | 3 + .../test/integration/good/production_test.go | 7 + .../test/reqserving-e2e/good/production.go | 3 + .../reqserving-e2e/good/production_test.go | 7 + .../testfuncstructure/testfuncstructure.go | 755 ++++++++++++++++++ .../testfuncstructure_test.go | 87 ++ hack/tools/hypershiftlinter/plugin.go | 2 + hack/tools/hypershiftlinter/plugin_test.go | 14 + 31 files changed, 1212 insertions(+) create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/ambiguous/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/ambiguous/production_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/external/external.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/external/external_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/generated.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/generated_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/production_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacyambiguous/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacyambiguous/production_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/precedence/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/precedence/production_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/scenarioonly/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/scenarioonly/production_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/split/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/split/production_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/e2e/good/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/e2e/good/production_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/envtest/good/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/envtest/good/production_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/integration/good/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/integration/good/production_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/reqserving-e2e/good/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/reqserving-e2e/good/production_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go new file mode 100644 index 000000000000..c9e105c4e80a --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go @@ -0,0 +1,5 @@ +package testfuncstructure + +// legacyExceptions contains exact pre-existing test declarations that do not +// satisfy TESTING.md. Remove an entry when the corresponding test is migrated. +var legacyExceptions = map[string]struct{}{} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/ambiguous/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/ambiguous/production.go new file mode 100644 index 000000000000..059ecb51b593 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/ambiguous/production.go @@ -0,0 +1,9 @@ +package ambiguous + +type First struct{} + +func (*First) Reconcile() {} + +type Second struct{} + +func (*Second) Reconcile() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/ambiguous/production_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/ambiguous/production_test.go new file mode 100644 index 000000000000..db6b0edc2c01 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/ambiguous/production_test.go @@ -0,0 +1,23 @@ +package ambiguous + +import "testing" + +func TestReconcile(t *testing.T) { // want `test function "TestReconcile" does not map unambiguously to one production function \(candidates: First.Reconcile, Second.Reconcile\); use the canonical Test or Test_ form, or add a documented //nolint:hypershiftlinter exception` +} + +func TestLifecycle(t *testing.T) { // want `test function "TestLifecycle" does not map unambiguously to one production function \(candidates: First.Reconcile, Second.Reconcile\); use the canonical Test or Test_ form, or add a documented //nolint:hypershiftlinter exception` + first := &First{} + first.Reconcile() + second := &Second{} + second.Reconcile() +} + +func TestFirst_Reconcile(t *testing.T) { + first := &First{} + first.Reconcile() +} + +func TestSecondReconcile(t *testing.T) { + second := &Second{} + second.Reconcile() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/external/external.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/external/external.go new file mode 100644 index 000000000000..7a0331a702c5 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/external/external.go @@ -0,0 +1,17 @@ +package external + +func Validate() {} + +type Worker struct{} + +func (*Worker) Run() {} + +func hidden() {} + +type hiddenWorker struct{} + +func NewHiddenWorker() *hiddenWorker { + return &hiddenWorker{} +} + +func (*hiddenWorker) Execute() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/external/external_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/external/external_test.go new file mode 100644 index 000000000000..80fe4cec31d5 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/external/external_test.go @@ -0,0 +1,30 @@ +package external_test + +import ( + "testing" + + "a/external" +) + +func TestValidate(t *testing.T) { + external.Validate() +} + +func TestValidateErrors(t *testing.T) { // want `test function "TestValidateErrors" also tests Validate; consolidate it into "TestValidate" using table-driven cases or t.Run subtests` + external.Validate() +} + +func TestWorker_Run(t *testing.T) { + worker := &external.Worker{} + worker.Run() +} + +func TestWorker_RunError(t *testing.T) { // want `test function "TestWorker_RunError" also tests Worker.Run; consolidate it into "TestWorker_Run" using table-driven cases or t.Run subtests` + worker := &external.Worker{} + worker.Run() +} + +func TestHiddenWorker_Execute(t *testing.T) { + worker := external.NewHiddenWorker() + worker.Execute() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/generated.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/generated.go new file mode 100644 index 000000000000..9c803dc594d2 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/generated.go @@ -0,0 +1,5 @@ +// Code generated by test fixture. DO NOT EDIT. + +package good + +func GeneratedUtility() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/generated_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/generated_test.go new file mode 100644 index 000000000000..2ebab1eb2ffe --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/generated_test.go @@ -0,0 +1,9 @@ +// Code generated by test fixture. DO NOT EDIT. + +package good + +import "testing" + +func TestReconcileGenerated(t *testing.T) { + Reconcile() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/production.go new file mode 100644 index 000000000000..440dd5b3058a --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/production.go @@ -0,0 +1,25 @@ +package good + +func Reconcile() {} + +func buildThing() {} + +func ReconcileLonger() {} + +type Controller struct{} + +func (*Controller) Sync() {} + +func (*Controller) reconcile() {} + +type OtherController struct{} + +func (*OtherController) Sync() {} + +type Starter struct{} + +func (*Starter) Start() {} + +type worker struct{} + +func (*worker) Run() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/production_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/production_test.go new file mode 100644 index 000000000000..22174ee7d5f7 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/production_test.go @@ -0,0 +1,99 @@ +package good + +import ( + "fmt" + "testing" +) + +func TestReconcile(t *testing.T) { + t.Run("When input is valid, it should reconcile", func(t *testing.T) { + Reconcile() + }) + t.Run("When input is invalid, it should return an error", func(t *testing.T) { + Reconcile() + }) +} + +func TestBuildThing(t *testing.T) { + tests := []struct { + name string + }{ + {name: "When the input is valid, it should build the thing"}, + {name: "When the input is empty, it should build the default thing"}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + buildThing() + }) + } +} + +func TestReconcileLonger(t *testing.T) { + ReconcileLonger() +} + +func TestController_Sync(t *testing.T) { + controller := &Controller{} + controller.Sync() +} + +func TestController_Reconcile(t *testing.T) { + controller := &Controller{} + controller.reconcile() +} + +// A bare method test is accepted when its call identifies one receiver. +func TestSync(t *testing.T) { + controller := &OtherController{} + controller.Sync() +} + +// A bare method test is accepted without call evidence when only one method has +// that name. +func TestStart(t *testing.T) {} + +func TestWorker_Run(t *testing.T) { + instance := &worker{} + instance.Run() +} + +// Package-level behavior with no production function call has no meaningful +// one-function mapping. +func TestConstants(t *testing.T) { + if fmt.Sprint(1) != "1" { + t.Fatal("unexpected formatting") + } +} + +func testHelper() {} + +func TestTestHelper(t *testing.T) { + testHelper() +} + +func TestGeneratedUtilityScenario(t *testing.T) { + GeneratedUtility() +} + +//nolint:hypershiftlinter // testfuncstructure: intentional compatibility scenario +func TestReconcileCompatibility(t *testing.T) { + Reconcile() +} + +func TestMain(m *testing.M) {} + +func BenchmarkReconcile(b *testing.B) {} + +func FuzzReconcile(f *testing.F) {} + +func ExampleReconcile() {} + +type suite struct{} + +func (*suite) TestReconcile(t *testing.T) {} + +func Test_Reconcile(t *testing.T) {} + +func Test(t *testing.T) { // want `test function "Test" must be named "TestReconcile" to map to Reconcile; use one top-level test with table-driven cases or t.Run subtests, or add a documented //nolint:hypershiftlinter exception` + Reconcile() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production.go new file mode 100644 index 000000000000..6d00a436e956 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production.go @@ -0,0 +1,3 @@ +package legacy + +func Reconcile() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production_test.go new file mode 100644 index 000000000000..abd71f17744e --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production_test.go @@ -0,0 +1,11 @@ +package legacy + +import "testing" + +func TestReconcile(t *testing.T) { // want `test function "TestReconcile" adds another top-level test for Reconcile while legacy test "TestReconcileErrors" still exists; consolidate both under "TestReconcile" using table-driven cases or t.Run subtests` + Reconcile() +} + +func TestReconcileErrors(t *testing.T) { + Reconcile() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacyambiguous/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacyambiguous/production.go new file mode 100644 index 000000000000..0082d65671b2 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacyambiguous/production.go @@ -0,0 +1,5 @@ +package legacyambiguous + +func Alpha() {} + +func Beta() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacyambiguous/production_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacyambiguous/production_test.go new file mode 100644 index 000000000000..7a1e050a2eb9 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacyambiguous/production_test.go @@ -0,0 +1,12 @@ +package legacyambiguous + +import "testing" + +func TestWorkflow(t *testing.T) { + Alpha() + Beta() +} + +func TestAlpha(t *testing.T) { // want `test function "TestAlpha" adds another top-level test for Alpha while legacy test "TestWorkflow" still exists; consolidate both under "TestAlpha" using table-driven cases or t.Run subtests` + Alpha() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/precedence/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/precedence/production.go new file mode 100644 index 000000000000..4dbbfd0454d7 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/precedence/production.go @@ -0,0 +1,7 @@ +package precedence + +func Reconcile() {} + +type Controller struct{} + +func (*Controller) Reconcile() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/precedence/production_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/precedence/production_test.go new file mode 100644 index 000000000000..5ccf6afdab4f --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/precedence/production_test.go @@ -0,0 +1,6 @@ +package precedence + +import "testing" + +// The exact package-function name takes precedence over the bare method alias. +func TestReconcile(t *testing.T) {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/scenarioonly/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/scenarioonly/production.go new file mode 100644 index 000000000000..ea4b30177ebf --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/scenarioonly/production.go @@ -0,0 +1,3 @@ +package scenarioonly + +func Reconcile() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/scenarioonly/production_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/scenarioonly/production_test.go new file mode 100644 index 000000000000..8763fcf9c847 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/scenarioonly/production_test.go @@ -0,0 +1,11 @@ +package scenarioonly + +import "testing" + +func TestReconcileHappy(t *testing.T) { // want `test function "TestReconcileHappy" must be named "TestReconcile" for Reconcile; keep scenarios in table-driven cases or t.Run subtests` + Reconcile() +} + +func TestReconcileError(t *testing.T) { // want `test function "TestReconcileError" must be named "TestReconcile" for Reconcile; keep scenarios in table-driven cases or t.Run subtests` + Reconcile() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/split/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/split/production.go new file mode 100644 index 000000000000..f78462ae6e4f --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/split/production.go @@ -0,0 +1,9 @@ +package split + +func Reconcile() {} + +func ReconcileErrors() {} + +type Controller struct{} + +func (*Controller) Sync() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/split/production_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/split/production_test.go new file mode 100644 index 000000000000..22d6f1ba0b29 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/split/production_test.go @@ -0,0 +1,25 @@ +package split + +import "testing" + +func TestReconcile(t *testing.T) { + Reconcile() +} + +func TestReconcileAggregatesErrors(t *testing.T) { // want `test function "TestReconcileAggregatesErrors" also tests Reconcile; consolidate it into "TestReconcile" using table-driven cases or t.Run subtests` + Reconcile() +} + +// The longer exact production name wins over the Reconcile prefix. +func TestReconcileErrors(t *testing.T) { + ReconcileErrors() +} + +func TestController_SyncFailure(t *testing.T) { // want `test function "TestController_SyncFailure" must be named "TestController_Sync" for Controller.Sync; keep scenarios in table-driven cases or t.Run subtests` + controller := &Controller{} + controller.Sync() +} + +func TestDisconnectedName(t *testing.T) { // want `test function "TestDisconnectedName" must be named "TestReconcile" to map to Reconcile; use one top-level test with table-driven cases or t.Run subtests, or add a documented //nolint:hypershiftlinter exception` + Reconcile() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/e2e/good/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/e2e/good/production.go new file mode 100644 index 000000000000..6ea8574b7ef4 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/e2e/good/production.go @@ -0,0 +1,3 @@ +package good + +func Reconcile() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/e2e/good/production_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/e2e/good/production_test.go new file mode 100644 index 000000000000..e5c5893793a8 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/e2e/good/production_test.go @@ -0,0 +1,7 @@ +package good + +import "testing" + +func TestReconcileErrors(t *testing.T) { + Reconcile() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/envtest/good/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/envtest/good/production.go new file mode 100644 index 000000000000..6ea8574b7ef4 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/envtest/good/production.go @@ -0,0 +1,3 @@ +package good + +func Reconcile() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/envtest/good/production_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/envtest/good/production_test.go new file mode 100644 index 000000000000..e5c5893793a8 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/envtest/good/production_test.go @@ -0,0 +1,7 @@ +package good + +import "testing" + +func TestReconcileErrors(t *testing.T) { + Reconcile() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/integration/good/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/integration/good/production.go new file mode 100644 index 000000000000..6ea8574b7ef4 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/integration/good/production.go @@ -0,0 +1,3 @@ +package good + +func Reconcile() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/integration/good/production_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/integration/good/production_test.go new file mode 100644 index 000000000000..e5c5893793a8 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/integration/good/production_test.go @@ -0,0 +1,7 @@ +package good + +import "testing" + +func TestReconcileErrors(t *testing.T) { + Reconcile() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/reqserving-e2e/good/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/reqserving-e2e/good/production.go new file mode 100644 index 000000000000..6ea8574b7ef4 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/reqserving-e2e/good/production.go @@ -0,0 +1,3 @@ +package good + +func Reconcile() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/reqserving-e2e/good/production_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/reqserving-e2e/good/production_test.go new file mode 100644 index 000000000000..e5c5893793a8 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/reqserving-e2e/good/production_test.go @@ -0,0 +1,7 @@ +package good + +import "testing" + +func TestReconcileErrors(t *testing.T) { + Reconcile() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go new file mode 100644 index 000000000000..19b2bb017138 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go @@ -0,0 +1,755 @@ +package testfuncstructure + +import ( + "fmt" + "go/ast" + "go/build/constraint" + "go/token" + "go/types" + "path/filepath" + "slices" + "strings" + "unicode" + "unicode/utf8" + + "github.com/openshift/hypershift/hack/tools/hypershiftlinter/analyzers/pathutil" + + "golang.org/x/tools/go/analysis" +) + +var Analyzer = &analysis.Analyzer{ + Name: "testfuncstructure", + Doc: "checks that unit tests map one-to-one to production functions and methods", + Run: run, +} + +type productionSymbol struct { + id string + display string + canonicalTest string + variants []testNameVariant +} + +type testNameVariant struct { + name string + priority int +} + +type testFunction struct { + decl *ast.FuncDecl + filename string + name string + suppressed bool + resolution resolution +} + +type resolution struct { + kind resolutionKind + target *productionSymbol + candidates []*productionSymbol + priority int +} + +type resolutionKind int + +const ( + resolutionIgnored resolutionKind = iota + resolutionExact + resolutionScenario + resolutionDisconnected + resolutionAmbiguous +) + +type symbolMatch struct { + symbol *productionSymbol + kind resolutionKind + prefixLen int + priority int +} + +var excludedBuildTags = map[string]struct{}{ + "e2e": {}, + "e2ev2": {}, + "envtest": {}, + "integration": {}, + "reqserving": {}, +} + +func run(pass *analysis.Pass) (any, error) { + symbols := collectProductionSymbols(pass) + symbolsByID := make(map[string]*productionSymbol, len(symbols)) + for _, symbol := range symbols { + symbolsByID[symbol.id] = symbol + } + + tests := collectTests(pass) + groups := map[string][]*testFunction{} + for _, test := range tests { + test.resolution = resolveTest(pass, test, symbols, symbolsByID) + switch test.resolution.kind { + case resolutionExact, resolutionScenario, resolutionDisconnected: + groups[test.resolution.target.id] = append(groups[test.resolution.target.id], test) + case resolutionAmbiguous: + reportAmbiguous(pass, test) + if isLegacyException(pass, test) { + for _, candidate := range test.resolution.candidates { + legacyCandidate := *test + legacyCandidate.resolution = resolution{kind: resolutionDisconnected, target: candidate} + groups[candidate.id] = append(groups[candidate.id], &legacyCandidate) + } + } + } + } + + groupIDs := make([]string, 0, len(groups)) + for id := range groups { + groupIDs = append(groupIDs, id) + } + slices.Sort(groupIDs) + for _, id := range groupIDs { + reportGroup(pass, groups[id]) + } + + return nil, nil +} + +func collectProductionSymbols(pass *analysis.Pass) []*productionSymbol { + seen := map[string]*productionSymbol{} + if target := externalPackageUnderTest(pass); target != nil { + collectExportedSymbols(target, seen) + } else { + for _, file := range pass.Files { + filename := pass.Fset.File(file.Pos()).Name() + if strings.HasSuffix(filename, "_test.go") || ast.IsGenerated(file) { + continue + } + for _, decl := range file.Decls { + fn, ok := decl.(*ast.FuncDecl) + if !ok { + continue + } + obj, ok := pass.TypesInfo.Defs[fn.Name].(*types.Func) + if ok { + addProductionSymbol(seen, obj) + } + } + } + } + + symbols := make([]*productionSymbol, 0, len(seen)) + for _, symbol := range seen { + symbols = append(symbols, symbol) + } + slices.SortFunc(symbols, func(a, b *productionSymbol) int { + return strings.Compare(a.id, b.id) + }) + return symbols +} + +func externalPackageUnderTest(pass *analysis.Pass) *types.Package { + if !strings.HasSuffix(pass.Pkg.Name(), "_test") { + return nil + } + + name := strings.TrimSuffix(pass.Pkg.Name(), "_test") + wantPath := strings.TrimSuffix(pass.Pkg.Path(), "_test") + var candidates []*types.Package + for _, imported := range pass.Pkg.Imports() { + if imported.Name() != name { + continue + } + if imported.Path() == wantPath { + return imported + } + candidates = append(candidates, imported) + } + if len(candidates) == 1 { + return candidates[0] + } + return nil +} + +func collectExportedSymbols(pkg *types.Package, seen map[string]*productionSymbol) { + for _, name := range pkg.Scope().Names() { + obj := pkg.Scope().Lookup(name) + switch obj := obj.(type) { + case *types.Func: + if obj.Exported() { + addProductionSymbol(seen, obj) + } + case *types.TypeName: + named, ok := types.Unalias(obj.Type()).(*types.Named) + if !ok { + continue + } + collectExportedMethods(pkg, named, types.NewMethodSet(named), seen) + collectExportedMethods(pkg, named, types.NewMethodSet(types.NewPointer(named)), seen) + } + } +} + +func collectExportedMethods(pkg *types.Package, receiver *types.Named, methodSet *types.MethodSet, seen map[string]*productionSymbol) { + for method := range methodSet.Methods() { + fn, ok := method.Obj().(*types.Func) + if !ok || !fn.Exported() || fn.Pkg() != pkg || receiverName(fn) != receiver.Obj().Name() { + continue + } + addProductionSymbol(seen, fn) + } +} + +func addProductionSymbol(seen map[string]*productionSymbol, fn *types.Func) { + if fn.Pkg() == nil { + return + } + + receiver := receiverName(fn) + testReceiver := upperFirst(receiver) + name := upperFirst(fn.Name()) + display := fn.Name() + canonical := "Test" + name + variants := []testNameVariant{{name: canonical, priority: 3}} + if receiver != "" { + display = receiver + "." + fn.Name() + canonical = "Test" + testReceiver + "_" + name + variants = []testNameVariant{ + {name: canonical, priority: 3}, + {name: "Test" + testReceiver + name, priority: 2}, + {name: "Test" + name, priority: 1}, + } + } + + id := symbolID(fn) + seen[id] = &productionSymbol{ + id: id, + display: display, + canonicalTest: canonical, + variants: deduplicateVariants(variants), + } +} + +func deduplicateVariants(variants []testNameVariant) []testNameVariant { + result := make([]testNameVariant, 0, len(variants)) + for _, candidate := range variants { + duplicate := false + for _, existing := range result { + if candidate.name == existing.name { + duplicate = true + break + } + } + if !duplicate { + result = append(result, candidate) + } + } + return result +} + +func receiverName(fn *types.Func) string { + signature, ok := fn.Type().(*types.Signature) + if !ok || signature.Recv() == nil { + return "" + } + typ := types.Unalias(signature.Recv().Type()) + if pointer, ok := typ.(*types.Pointer); ok { + typ = types.Unalias(pointer.Elem()) + } + named, ok := typ.(*types.Named) + if !ok { + return "" + } + return named.Obj().Name() +} + +func symbolID(fn *types.Func) string { + receiver := receiverName(fn) + if receiver == "" { + return fn.Pkg().Path() + "." + fn.Name() + } + return fn.Pkg().Path() + "." + receiver + "." + fn.Name() +} + +func upperFirst(value string) string { + r, size := utf8.DecodeRuneInString(value) + if r == utf8.RuneError && size == 0 { + return value + } + return string(unicode.ToUpper(r)) + value[size:] +} + +func collectTests(pass *analysis.Pass) []*testFunction { + var tests []*testFunction + for _, file := range pass.Files { + filename := pass.Fset.File(file.Pos()).Name() + if !isUnitTestFile(filename, file) { + continue + } + for _, decl := range file.Decls { + fn, ok := decl.(*ast.FuncDecl) + if !ok || !isOrdinaryTest(pass, fn) { + continue + } + tests = append(tests, &testFunction{ + decl: fn, + filename: filename, + name: fn.Name.Name, + suppressed: hasNoLintDirective(fn), + }) + } + } + slices.SortFunc(tests, func(a, b *testFunction) int { + if result := strings.Compare(a.filename, b.filename); result != 0 { + return result + } + return cmpPos(a.decl.Pos(), b.decl.Pos()) + }) + return tests +} + +func isUnitTestFile(filename string, file *ast.File) bool { + if !pathutil.IsUnitTest(filename) || ast.IsGenerated(file) { + return false + } + + normalized := filepath.ToSlash(filename) + for _, excludedPath := range []string{"/test/envtest/", "/test/reqserving-e2e/"} { + if strings.Contains(normalized, excludedPath) { + return false + } + } + return !hasExcludedBuildTag(file) +} + +func hasExcludedBuildTag(file *ast.File) bool { + var plusBuildExpressions []constraint.Expr + for _, group := range file.Comments { + if group.Pos() > file.Package { + break + } + for _, comment := range group.List { + switch { + case constraint.IsGoBuild(comment.Text): + expression, err := constraint.Parse(comment.Text) + return err == nil && requiresExcludedBuildTag(expression) + case constraint.IsPlusBuild(comment.Text): + expression, err := constraint.Parse(comment.Text) + if err == nil { + plusBuildExpressions = append(plusBuildExpressions, expression) + } + } + } + } + if len(plusBuildExpressions) == 0 { + return false + } + expression := plusBuildExpressions[0] + for _, next := range plusBuildExpressions[1:] { + expression = &constraint.AndExpr{X: expression, Y: next} + } + return requiresExcludedBuildTag(expression) +} + +func requiresExcludedBuildTag(expression constraint.Expr) bool { + otherTags := map[string]struct{}{} + collectOtherBuildTags(expression, otherTags) + if len(otherTags) > 16 { + return false + } + + tags := make([]string, 0, len(otherTags)) + for tag := range otherTags { + tags = append(tags, tag) + } + slices.Sort(tags) + for assignment := range 1 << len(tags) { + values := make(map[string]bool, len(tags)) + for index, tag := range tags { + values[tag] = assignment&(1< 0 { + return resolveMatches(highestPriorityMatches(exactMatches), called) + } + if len(scenarioMatches) > 0 { + longest := 0 + for _, match := range scenarioMatches { + longest = max(longest, match.prefixLen) + } + scenarioMatches = slices.DeleteFunc(scenarioMatches, func(match symbolMatch) bool { + return match.prefixLen != longest + }) + return resolveMatches(highestPriorityMatches(scenarioMatches), called) + } + + calledSymbols := make([]*productionSymbol, 0, len(called)) + for id := range called { + calledSymbols = append(calledSymbols, symbolsByID[id]) + } + slices.SortFunc(calledSymbols, func(a, b *productionSymbol) int { + return strings.Compare(a.id, b.id) + }) + if len(calledSymbols) == 1 { + return resolution{kind: resolutionDisconnected, target: calledSymbols[0]} + } + if len(calledSymbols) > 1 { + return resolution{kind: resolutionAmbiguous, candidates: calledSymbols} + } + return resolution{kind: resolutionIgnored} +} + +func matchSymbol(testName string, symbol *productionSymbol) (symbolMatch, bool) { + var best symbolMatch + found := false + for _, variant := range symbol.variants { + kind, ok := matchTestName(testName, variant.name) + if !ok { + continue + } + candidate := symbolMatch{ + symbol: symbol, + kind: kind, + prefixLen: len(variant.name), + priority: variant.priority, + } + if !found || betterMatch(candidate, best) { + best = candidate + found = true + } + } + return best, found +} + +func matchTestName(testName, productionTestName string) (resolutionKind, bool) { + if testName == productionTestName { + return resolutionExact, true + } + if len(testName) <= len(productionTestName) || !strings.HasPrefix(testName, productionTestName) { + return resolutionIgnored, false + } + remainder := testName[len(productionTestName):] + first, _ := utf8.DecodeRuneInString(remainder) + if first != '_' && !unicode.IsUpper(first) && !unicode.IsDigit(first) { + return resolutionIgnored, false + } + return resolutionScenario, true +} + +func highestPriorityMatches(matches []symbolMatch) []symbolMatch { + highest := 0 + for _, match := range matches { + highest = max(highest, match.priority) + } + return slices.DeleteFunc(matches, func(match symbolMatch) bool { + return match.priority != highest + }) +} + +func betterMatch(candidate, current symbolMatch) bool { + if candidate.kind != current.kind { + return candidate.kind == resolutionExact + } + if candidate.prefixLen != current.prefixLen { + return candidate.prefixLen > current.prefixLen + } + return candidate.priority > current.priority +} + +func resolveMatches(matches []symbolMatch, called map[string]struct{}) resolution { + if len(matches) == 1 { + match := matches[0] + return resolution{kind: match.kind, target: match.symbol, priority: match.priority} + } + + calledMatches := slices.DeleteFunc(slices.Clone(matches), func(match symbolMatch) bool { + _, ok := called[match.symbol.id] + return !ok + }) + if len(calledMatches) == 1 { + match := calledMatches[0] + return resolution{kind: match.kind, target: match.symbol, priority: match.priority} + } + + candidates := make([]*productionSymbol, 0, len(matches)) + for _, match := range matches { + candidates = append(candidates, match.symbol) + } + slices.SortFunc(candidates, func(a, b *productionSymbol) int { + return strings.Compare(a.id, b.id) + }) + return resolution{kind: resolutionAmbiguous, candidates: candidates} +} + +func calledProductionSymbols(pass *analysis.Pass, test *ast.FuncDecl, symbolsByID map[string]*productionSymbol) map[string]struct{} { + called := map[string]struct{}{} + ast.Inspect(test.Body, func(node ast.Node) bool { + call, ok := node.(*ast.CallExpr) + if !ok { + return true + } + fn := calledFunction(pass, call.Fun) + if fn == nil || fn.Pkg() == nil { + return true + } + id := symbolID(fn) + if _, ok := symbolsByID[id]; ok { + called[id] = struct{}{} + } + return true + }) + return called +} + +func calledFunction(pass *analysis.Pass, expression ast.Expr) *types.Func { + switch expression := expression.(type) { + case *ast.ParenExpr: + return calledFunction(pass, expression.X) + case *ast.IndexExpr: + return calledFunction(pass, expression.X) + case *ast.IndexListExpr: + return calledFunction(pass, expression.X) + case *ast.Ident: + fn, _ := pass.TypesInfo.Uses[expression].(*types.Func) + return fn + case *ast.SelectorExpr: + if selection := pass.TypesInfo.Selections[expression]; selection != nil { + fn, _ := selection.Obj().(*types.Func) + return fn + } + fn, _ := pass.TypesInfo.Uses[expression.Sel].(*types.Func) + return fn + default: + return nil + } +} + +func reportAmbiguous(pass *analysis.Pass, test *testFunction) { + if shouldSuppress(pass, test) { + return + } + names := make([]string, 0, len(test.resolution.candidates)) + for _, candidate := range test.resolution.candidates { + names = append(names, candidate.display) + } + pass.Report(analysis.Diagnostic{ + Pos: test.decl.Name.Pos(), + End: test.decl.Name.End(), + Message: fmt.Sprintf("test function %q does not map unambiguously to one production function (candidates: %s); use the canonical Test or Test_ form, or add a documented //nolint:hypershiftlinter exception", test.name, strings.Join(names, ", ")), + }) +} + +func reportGroup(pass *analysis.Pass, tests []*testFunction) { + slices.SortFunc(tests, func(a, b *testFunction) int { + if a.resolution.kind != b.resolution.kind { + if a.resolution.kind == resolutionExact { + return -1 + } + if b.resolution.kind == resolutionExact { + return 1 + } + } + if a.resolution.priority != b.resolution.priority { + return b.resolution.priority - a.resolution.priority + } + if result := strings.Compare(a.filename, b.filename); result != 0 { + return result + } + return cmpPos(a.decl.Pos(), b.decl.Pos()) + }) + + symbol := tests[0].resolution.target + preferred := symbol.canonicalTest + var primary *testFunction + if tests[0].resolution.kind == resolutionExact { + primary = tests[0] + preferred = primary.name + } + if primary != nil && !isLegacyException(pass, primary) && !primary.suppressed { + for _, test := range tests[1:] { + if !isLegacyException(pass, test) { + continue + } + pass.Report(analysis.Diagnostic{ + Pos: primary.decl.Name.Pos(), + End: primary.decl.Name.End(), + Message: fmt.Sprintf("test function %q adds another top-level test for %s while legacy test %q still exists; consolidate both under %q using table-driven cases or t.Run subtests", primary.name, symbol.display, test.name, preferred), + Related: []analysis.RelatedInformation{{ + Pos: test.decl.Name.Pos(), + End: test.decl.Name.End(), + Message: fmt.Sprintf("legacy top-level test %q", test.name), + }}, + }) + break + } + } + + for _, test := range tests { + if shouldSuppress(pass, test) { + continue + } + + var message string + switch test.resolution.kind { + case resolutionExact: + if test == primary { + continue + } + message = fmt.Sprintf("test function %q duplicates %q for %s; consolidate scenarios under %q using table-driven cases or t.Run subtests", test.name, primary.name, symbol.display, preferred) + case resolutionScenario: + if primary != nil { + message = fmt.Sprintf("test function %q also tests %s; consolidate it into %q using table-driven cases or t.Run subtests", test.name, symbol.display, preferred) + } else { + message = fmt.Sprintf("test function %q must be named %q for %s; keep scenarios in table-driven cases or t.Run subtests", test.name, preferred, symbol.display) + } + case resolutionDisconnected: + message = fmt.Sprintf("test function %q must be named %q to map to %s; use one top-level test with table-driven cases or t.Run subtests, or add a documented //nolint:hypershiftlinter exception", test.name, preferred, symbol.display) + default: + continue + } + + diagnostic := analysis.Diagnostic{ + Pos: test.decl.Name.Pos(), + End: test.decl.Name.End(), + Message: message, + } + if primary != nil && primary != test { + diagnostic.Related = []analysis.RelatedInformation{{ + Pos: primary.decl.Name.Pos(), + End: primary.decl.Name.End(), + Message: fmt.Sprintf("existing top-level test %q", primary.name), + }} + } + pass.Report(diagnostic) + } +} + +func shouldSuppress(pass *analysis.Pass, test *testFunction) bool { + if test.suppressed { + return true + } + return isLegacyException(pass, test) +} + +func isLegacyException(pass *analysis.Pass, test *testFunction) bool { + _, ok := legacyExceptions[legacyKey(pass, test)] + return ok +} + +func legacyKey(pass *analysis.Pass, test *testFunction) string { + const modulePath = "github.com/openshift/hypershift" + + packagePath := strings.TrimSuffix(pass.Pkg.Path(), "_test") + if packagePath == modulePath { + return filepath.Base(test.filename) + "|" + test.name + } + if relativePackage, ok := strings.CutPrefix(packagePath, modulePath+"/"); ok { + return relativePackage + "/" + filepath.Base(test.filename) + "|" + test.name + } + return packagePath + "/" + filepath.Base(test.filename) + "|" + test.name +} + +func cmpPos(a, b token.Pos) int { + switch { + case a < b: + return -1 + case a > b: + return 1 + default: + return 0 + } +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go new file mode 100644 index 000000000000..c44010c07e62 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go @@ -0,0 +1,87 @@ +package testfuncstructure + +import ( + "go/parser" + "go/token" + "testing" + + "golang.org/x/tools/go/analysis/analysistest" +) + +func TestRun(t *testing.T) { + legacyFixtures := []string{ + "a/legacy/production_test.go|TestReconcileErrors", + "a/legacyambiguous/production_test.go|TestWorkflow", + } + for _, fixture := range legacyFixtures { + legacyExceptions[fixture] = struct{}{} + defer delete(legacyExceptions, fixture) + } + + testdata := analysistest.TestData() + analysistest.Run(t, testdata, Analyzer, + "a/good", + "a/split", + "a/ambiguous", + "a/external", + "a/legacy", + "a/legacyambiguous", + "a/precedence", + "a/scenarioonly", + "test/e2e/good", + "test/envtest/good", + "test/integration/good", + "test/reqserving-e2e/good", + ) +} + +func TestHasExcludedBuildTag(t *testing.T) { + tests := []struct { + name string + source string + excluded bool + }{ + { + name: "When an envtest build tag is present, it should exclude the file", + source: "//go:build envtest\n\npackage fixture\n", + excluded: true, + }, + { + name: "When an excluded tag is negated, it should retain the unit test file", + source: "//go:build linux && !envtest\n\npackage fixture\n", + excluded: false, + }, + { + name: "When an excluded tag is optional, it should retain the unit test file", + source: "//go:build linux || envtest\n\npackage fixture\n", + excluded: false, + }, + { + name: "When every build path requires an excluded tag, it should exclude the file", + source: "//go:build (linux && envtest) || integration\n\npackage fixture\n", + excluded: true, + }, + { + name: "When a legacy build constraint requires envtest, it should exclude the file", + source: "// +build envtest\n\npackage fixture\n", + excluded: true, + }, + { + name: "When no build tag is present, it should retain the unit test file", + source: "package fixture\n", + excluded: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + file, err := parser.ParseFile(token.NewFileSet(), "fixture_test.go", test.source, parser.ParseComments) + if err != nil { + t.Fatalf("parse fixture: %v", err) + } + if actual := hasExcludedBuildTag(file); actual != test.excluded { + t.Fatalf("expected excluded=%t, got %t", test.excluded, actual) + } + }) + } +} diff --git a/hack/tools/hypershiftlinter/plugin.go b/hack/tools/hypershiftlinter/plugin.go index f29f728fac4f..966c0a6a81ee 100644 --- a/hack/tools/hypershiftlinter/plugin.go +++ b/hack/tools/hypershiftlinter/plugin.go @@ -13,6 +13,7 @@ import ( "github.com/openshift/hypershift/hack/tools/hypershiftlinter/analyzers/sippyannotation" "github.com/openshift/hypershift/hack/tools/hypershiftlinter/analyzers/testcasename" "github.com/openshift/hypershift/hack/tools/hypershiftlinter/analyzers/testfuncname" + "github.com/openshift/hypershift/hack/tools/hypershiftlinter/analyzers/testfuncstructure" "github.com/openshift/hypershift/hack/tools/hypershiftlinter/analyzers/vacuouspass" "golang.org/x/tools/go/analysis" @@ -62,6 +63,7 @@ func allAnalyzers() []*analysis.Analyzer { return []*analysis.Analyzer{ testcasename.Analyzer, testfuncname.Analyzer, + testfuncstructure.Analyzer, sippyannotation.Analyzer, guestcluster.Analyzer, contextbackground.Analyzer, diff --git a/hack/tools/hypershiftlinter/plugin_test.go b/hack/tools/hypershiftlinter/plugin_test.go index d5dde9061631..514e5090e240 100644 --- a/hack/tools/hypershiftlinter/plugin_test.go +++ b/hack/tools/hypershiftlinter/plugin_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/openshift/hypershift/hack/tools/hypershiftlinter/analyzers/hcpstatuspatch" + "github.com/openshift/hypershift/hack/tools/hypershiftlinter/analyzers/testfuncstructure" "golang.org/x/tools/go/analysis" ) @@ -22,6 +23,19 @@ func TestBuildAnalyzers(t *testing.T) { t.Fatalf("expected only the HC and HCP status analyzer, got %v", analyzerNames(got)) } }) + + t.Run("When testfuncstructure is enabled, it should select the unit test structure analyzer", func(t *testing.T) { + raw := map[string]any{ + "analyzers": map[string]any{"enable": []any{"testfuncstructure"}}, + } + got, err := BuildAnalyzers(raw) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(got) != 1 || got[0] != testfuncstructure.Analyzer { + t.Fatalf("expected only the unit test structure analyzer, got %v", analyzerNames(got)) + } + }) } func TestBuildAnalyzersRejectsUnknownTopLevelField(t *testing.T) { From 510d3a8d0709ecc26aeefd63d8ed834ca9fa5f0a Mon Sep 17 00:00:00 2001 From: Bryan Cox Date: Tue, 22 Sep 2026 14:31:08 -0400 Subject: [PATCH 3/8] ci(lint): enforce unit test structure policy Enable testfuncstructure in root and API golangci configs and wire the plugin into API lint targets. Document mapping and exception rules, and baseline existing declarations by exact path and test name. This keeps current debt reviewable while new violations fail and follow-up changes can remove entries incrementally. Signed-off-by: Bryan Cox Commit-Message-Assisted-by: Claude (via Claude Code) --- .golangci.yml | 1 + Makefile | 6 +- TESTING.md | 18 +- api/.golangci.yml | 8 + hack/tools/hypershiftlinter/README.md | 48 +- .../analyzers/testfuncstructure/legacy.go | 849 +++++++++++++++++- 6 files changed, 922 insertions(+), 8 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 0989c6f07fa7..926187d5e48b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -32,6 +32,7 @@ linters: - hcpstatuspatch - testcasename - testfuncname + - testfuncstructure - sippyannotation - guestcluster - contextbackground diff --git a/Makefile b/Makefile index af3905b76d35..17caf9dcf562 100644 --- a/Makefile +++ b/Makefile @@ -119,11 +119,11 @@ UPSTREAM_REMOTE ?= $(shell git remote -v 2>/dev/null | grep 'openshift/hypershif PULL_BASE_SHA ?= $(if $(UPSTREAM_REMOTE),$(shell git rev-parse $(UPSTREAM_REMOTE)/main),$(shell git rev-parse main)) .PHONY: api-lint -api-lint: $(GOLANGCI_LINT) $(KUBEAPILINTER_PLUGIN) +api-lint: $(GOLANGCI_LINT) $(KUBEAPILINTER_PLUGIN) $(HYPERSHIFTLINTER_PLUGIN) cd api && $(GOLANGCI_LINT) run --config ./.golangci.yml --modules-download-mode=readonly -v --new-from-rev=${PULL_BASE_SHA} .PHONY: api-lint-fix -api-lint-fix: $(GOLANGCI_LINT) $(KUBEAPILINTER_PLUGIN) +api-lint-fix: $(GOLANGCI_LINT) $(KUBEAPILINTER_PLUGIN) $(HYPERSHIFTLINTER_PLUGIN) cd api && $(GOLANGCI_LINT) run --config ./.golangci.yml --fix -v --new-from-rev=${PULL_BASE_SHA} .PHONY: precommit-api-lint-fix @@ -156,7 +156,7 @@ hypershift-lint-all: $(GOLANGCI_LINT) $(HYPERSHIFTLINTER_PLUGIN) .PHONY: test-linter test-linter: - cd $(TOOLS_DIR) && $(GO) test ./hypershiftlinter/analyzers/... -count=1 + cd $(TOOLS_DIR) && $(GO) test ./hypershiftlinter/... -count=1 .PHONY: verify-git-clean verify-git-clean: diff --git a/TESTING.md b/TESTING.md index fc1a0f6a9e49..6e33d094fd11 100644 --- a/TESTING.md +++ b/TESTING.md @@ -45,7 +45,23 @@ func TestBuildAzureMachineTemplate(t *testing.T) { ... } Do not use generic or disconnected test function names like `TestNodePoolFeatures`, `TestAzureIntegration`, or `TestVariousCases`. -Each test function should map 1:1 to the function under test. If a function is complex enough to warrant many test cases, use table-driven tests within a single `Test` function with the "When...it should..." naming for each case. +Each production function or method must map to at most one top-level unit test function. If a function is complex enough to warrant many test cases, use table-driven tests or `t.Run` subtests within a single test function, with the "When...it should..." naming for each case. + +Prefer `Test_` for method tests so methods with common names such as `Reconcile`, `Validate`, and `Run` map unambiguously: + +```go +// Testing (*NodePoolReconciler).Reconcile +func TestNodePoolReconciler_Reconcile(t *testing.T) { ... } +``` + +For compatibility, `Test` and `Test` are also accepted when they resolve to exactly one production method. + +The one-to-one rule applies to ordinary unit tests. It does not apply to `TestMain`, benchmarks, fuzz tests, examples, generated files, integration/e2e/envtest/request-serving entry points, or package-level behavioral tests that do not correspond to one production function. For an intentional exception that still exercises a production function, add a declaration-level suppression with a specific reason: + +```go +//nolint:hypershiftlinter // testfuncstructure: validates compatibility across multiple functions +func TestSerializationCompatibility(t *testing.T) { ... } +``` ## Test Placement diff --git a/api/.golangci.yml b/api/.golangci.yml index 921572150302..ca6417f4d303 100644 --- a/api/.golangci.yml +++ b/api/.golangci.yml @@ -3,11 +3,19 @@ run: allow-parallel-runners: true linters: enable: + - hypershiftlinter - kubeapilinter disable: - staticcheck settings: custom: + hypershiftlinter: + path: ../hack/tools/bin/hypershiftlinter.so + description: "Enforces HyperShift unit test function structure" + settings: + analyzers: + enable: + - testfuncstructure kubeapilinter: path: ../hack/tools/bin/kube-api-linter.so description: kubeapilinter is the Kube-API-Linter and lints Kube like APIs based on API conventions and best practices. diff --git a/hack/tools/hypershiftlinter/README.md b/hack/tools/hypershiftlinter/README.md index 23fd1a410e15..d55447c02a25 100644 --- a/hack/tools/hypershiftlinter/README.md +++ b/hack/tools/hypershiftlinter/README.md @@ -30,7 +30,7 @@ relying on reviewer memory. That matters for several reasons: ## Analyzers -The plugin ships 9 analyzers, scoped so each rule only fires where it applies. +The plugin ships 10 analyzers, scoped so each rule only fires where it applies. ### Unit test conventions (`TESTING.md`, unit tests only) @@ -38,6 +38,7 @@ The plugin ships 9 analyzers, scoped so each rule only fires where it applies. | -------------- | ----------------------------------------------------------------------------------------- | | `testcasename` | Test case name fields match `When , it should `. | | `testfuncname` | Test functions do not use the `Test_` prefix; use `TestFunctionName` instead. | +| `testfuncstructure` | Top-level unit tests map one-to-one to production functions and methods. | ### E2E conventions (`test/e2e/v2/` only) @@ -69,12 +70,53 @@ Relevant Makefile targets: - `make hypershift-lint-all` — run the enabled custom analyzers against the current tree. - `make lint` — run normal lint checks, including these analyzers. -- `make test-linter` — run the analyzers' own unit tests - (`go test ./hypershiftlinter/analyzers/...`). +- `make test-linter` — run the plugin and analyzers' unit tests + (`go test ./hypershiftlinter/...`). Each analyzer has [`analysistest`](https://pkg.go.dev/golang.org/x/tools/go/analysis/analysistest)-based unit tests with good/bad `testdata/` fixtures. +## Unit test function structure and exceptions + +`testfuncstructure` maps ordinary `func TestXxx(*testing.T)` declarations to +production functions and methods using package type information. Package +functions use `Test`. The preferred method form is +`Test_`; a compact receiver form or bare method name +is accepted only when it resolves unambiguously. Longer exact production names +take precedence over scenario suffixes, so `TestReconcileErrors` maps to a real +`ReconcileErrors` function before it can be treated as another `Reconcile` +scenario. + +The analyzer is enabled by both the root and `api/` module lint configurations. + +When several top-level tests map to one production symbol, consolidate their +scenarios into table cases or `t.Run` subtests under one test function. The +analyzer does not infer runtime coverage or require tests for every production +function. It ignores test-only helpers, package-level behavioral tests without +a production-function call, special Go test entry points, generated files, and +integration-style suites. + +For an intentional exception, use the golangci-lint plugin name on the test +declaration and explain why no one-function mapping is appropriate: + +```go +//nolint:hypershiftlinter // testfuncstructure: validates compatibility across multiple functions +func TestSerializationCompatibility(t *testing.T) { ... } +``` + +`testfuncstructure` is an analyzer inside the `hypershiftlinter` plugin, not a +standalone golangci-lint linter, so `//nolint:testfuncstructure` does not work. +The suppression affects every `hypershiftlinter` diagnostic on that declaration; +keep it declaration-scoped and specific. + +Existing findings present when this rule was enabled are listed exactly by +package, file, and declaration in `analyzers/testfuncstructure/legacy.go`. These +entries are migration debt, not approved patterns. New declarations remain +enforced, including a new canonical declaration added beside a baselined split +test. Remove an entry when its test is renamed or consolidated. An accepted +limitation is that an existing declaration remains exempt while its baseline +entry exists, even if its body changes. + ## Legacy E2E utility migration exceptions `e2eutilallowlist` prevents new v2 dependencies on `test/e2e/util` and its diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go index c9e105c4e80a..7dd15673670a 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go @@ -2,4 +2,851 @@ package testfuncstructure // legacyExceptions contains exact pre-existing test declarations that do not // satisfy TESTING.md. Remove an entry when the corresponding test is migrated. -var legacyExceptions = map[string]struct{}{} +var legacyExceptions = map[string]struct{}{ + "api/util/ipnet/ipnet_test.go|TestUnmarshal": {}, + "cmd/cluster/agent/create_test.go|TestCreateCluster": {}, + "cmd/cluster/aws/create_test.go|TestCreateCluster": {}, + "cmd/cluster/azure/create_test.go|TestCreateCluster": {}, + "cmd/cluster/azure/create_test.go|TestCreateClusterAutoNodeRequiresKarpenterClientID": {}, + "cmd/cluster/azure/create_test.go|TestCredentialSecretHasDeleteWithClusterLabel": {}, + "cmd/cluster/azure/create_test.go|TestDNSZoneRGValidation": {}, + "cmd/cluster/azure/create_test.go|TestRoleAssignmentWithInfraJSON": {}, + "cmd/cluster/azure/create_test.go|TestValidateEndpointAccess": {}, + "cmd/cluster/azure/create_test.go|TestValidateOAuthPublishingStrategy": {}, + "cmd/cluster/azure/destroy_test.go|TestDestroyClusterSetsCloudFromHostedCluster": {}, + "cmd/cluster/azure/destroy_test.go|TestDestroyClusterSetsGracePeriodFromTopology": {}, + "cmd/cluster/core/create_test.go|TestAllocateNodeCIDRsFlag": {}, + "cmd/cluster/core/create_test.go|TestDisableMultiNetworkFlag": {}, + "cmd/cluster/core/create_test.go|TestGetServicePublishingStrategyMapping": {}, + "cmd/cluster/core/create_test.go|TestOVNKubernetesMTUFlag": {}, + "cmd/cluster/core/create_test.go|TestPrototypeResources": {}, + "cmd/cluster/gcp/create_test.go|TestCreateCluster": {}, + "cmd/cluster/gcp/create_test.go|TestValidateGCPOptions": {}, + "cmd/cluster/kubevirt/create_test.go|TestCreateCluster": {}, + "cmd/cluster/none/create_test.go|TestCreateCluster": {}, + "cmd/cluster/openstack/create_test.go|TestCreateCluster": {}, + "cmd/cluster/openstack/create_test.go|TestCreateOptions_Validate": {}, + "cmd/cluster/powervs/create_test.go|TestCreateCluster": {}, + "cmd/fix/dr_oidc_iam_test.go|TestDrOidcIamOptions_Defaults": {}, + "cmd/fix/dr_oidc_iam_test.go|TestDrOidcIamOptions_RunDryRun": {}, + "cmd/fix/dr_oidc_iam_test.go|TestOidcDiscoveryURL": {}, + "cmd/infra/aws/create_operator_roles_test.go|TestCreateOperatorRoles": {}, + "cmd/infra/aws/create_operator_roles_test.go|TestCreateOperatorRolesValidate": {}, + "cmd/infra/aws/destroy_test.go|TestEmptyBucket": {}, + "cmd/infra/aws/destroy_test.go|TestEmptyBucket_Pagination": {}, + "cmd/infra/aws/util/sts_test.go|TestNewSTSSession_Validation": {}, + "cmd/infra/aws/util/sts_test.go|TestNewSTSSession_ValidationOrder": {}, + "cmd/infra/aws/util/util_test.go|TestGetSession": {}, + "cmd/infra/aws/util/util_test.go|TestGetSession_DefaultChainRoleAssumption": {}, + "cmd/infra/aws/util/util_test.go|TestNewSession": {}, + "cmd/infra/aws/util/util_test.go|TestNewSession_ContextPropagation": {}, + "cmd/infra/aws/util/util_test.go|TestNewSession_UserAgent": {}, + "cmd/infra/azure/identities_test.go|TestGetWorkloadIdentityDefinitions": {}, + "cmd/infra/azure/identities_test.go|TestKarpenterWorkloadIdentityDefinition": {}, + "cmd/infra/azure/rbac_test.go|TestAssignRole": {}, + "cmd/infra/azure/rbac_test.go|TestAssignRoleCreateParameters": {}, + "cmd/infra/gcp/create_infra_test.go|TestNetworkManagerFormatNames": {}, + "cmd/infra/gcp/destroy_iam_test.go|TestDestroyIAMNewDestroyIAMCommand": {}, + "cmd/install/assets/crds/assets_test.go|TestCapiResources": {}, + "cmd/install/assets/crds/assets_test.go|TestCustomResourceDefinitionsStorageVersion": {}, + "cmd/install/assets/hypershift_operator_test.go|TestExternalDNSDeployment_Build": {}, + "cmd/install/assets/hypershift_operator_test.go|TestHyperShiftOperatorClusterRole_WebhookRBAC": {}, + "cmd/install/assets/hypershift_operator_test.go|TestHyperShiftOperatorDeployment_Build": {}, + "cmd/install/assets/hypershift_operator_test.go|TestHyperShiftOperatorDeployment_Build_WorkloadIdentityLabel": {}, + "cmd/install/assets/resource_requirements_test.go|TestExternalDNSDeployment_HasResourceRequests": {}, + "cmd/install/assets/resource_requirements_test.go|TestHyperShiftOperatorDeployment_HasResourceRequests": {}, + "cmd/install/install_test.go|TestCRDIncludeFilter": {}, + "cmd/install/install_test.go|TestHyperShiftOperatorManifests_SharedIngress": {}, + "cmd/install/install_test.go|TestHyperShiftOperatorManifests_WebhookFlags": {}, + "cmd/install/install_test.go|TestNewInstallOptionsWithDefaults": {}, + "cmd/install/install_test.go|TestNewInstallOptionsWithDefaults_InstallScope": {}, + "cmd/install/install_test.go|TestOutputsHelpers": {}, + "cmd/install/install_test.go|TestRenderHyperShiftOperator_RenderSensitive": {}, + "cmd/install/install_test.go|TestRenderOutputsScope": {}, + "cmd/install/install_test.go|TestSetupCRDs": {}, + "cmd/install/install_test.go|TestSetupCRDs_CAPIStorageVersionMigrationGuard": {}, + "cmd/nodepool/aws/create_test.go|TestCreateNodePool_When_flags_are_parsed_it_should_generate_correct_nodepool": {}, + "cmd/nodepool/aws/create_test.go|TestUpdateNodePool_When_instance_profile_is_empty_it_should_default_to_infraID": {}, + "cmd/nodepool/aws/create_test.go|TestUpdateNodePool_When_instance_type_is_empty_it_should_default_based_on_arch": {}, + "cmd/nodepool/aws/create_test.go|TestUpdateNodePool_When_subnet_is_empty_it_should_use_hostedcluster_subnet": {}, + "cmd/nodepool/aws/create_test.go|TestValidate_When_root_volume_size_is_too_small_it_should_return_error": {}, + "cmd/nodepool/aws/create_test.go|TestValidate_When_root_volume_size_is_valid_it_should_succeed": {}, + "cmd/nodepool/azure/create_test.go|TestAzureBoundaryConditions": {}, + "cmd/nodepool/azure/create_test.go|TestCreateNodePool_When_flags_are_parsed_it_should_generate_correct_nodepool": {}, + "cmd/nodepool/azure/create_test.go|TestNodePoolPlatformImageGeneration": {}, + "cmd/nodepool/azure/create_test.go|TestValidateImageGeneration": {}, + "cmd/nodepool/gcp/create_test.go|TestCLIFlow": {}, + "cmd/nodepool/kubevirt/create_test.go|TestCreateNodePool_When_flags_are_parsed_it_should_generate_correct_nodepool": {}, + "cmd/nodepool/kubevirt/create_test.go|TestNodePoolPlatform_When_memory_is_set_it_should_parse_correctly": {}, + "cmd/nodepool/kubevirt/create_test.go|TestRawKubevirtPlatformCreateOptions_Validate": {}, + "cmd/nodepool/openstack/create_test.go|TestCreateNodePool_When_flags_are_parsed_it_should_generate_correct_nodepool": {}, + "cmd/nodepool/openstack/create_test.go|TestRawOpenStackPlatformCreateOptions_Validate": {}, + "cmd/oadp/backup_test.go|TestBackupCommandIncludedNamespacesFlag": {}, + "cmd/oadp/backup_test.go|TestCreateOptionsDefaults": {}, + "cmd/oadp/backup_test.go|TestGenerateBackupObject": {}, + "cmd/oadp/backup_test.go|TestGenerateBackupObjectComprehensive": {}, + "cmd/oadp/backup_test.go|TestGenerateBackupObjectWithCustomName": {}, + "cmd/oadp/backup_test.go|TestGenerateBackupObjectWithIncludedNamespaces": {}, + "cmd/oadp/backup_test.go|TestGenerateBackupObjectWithKubeVirtResourcePolicy": {}, + "cmd/oadp/common_test.go|TestApplyPlatformBackupSpec": {}, + "cmd/oadp/common_test.go|TestApplyPlatformBackupSpecPreservesExistingFields": {}, + "cmd/oadp/restore_test.go|TestBuildIncludedNamespaces": {}, + "cmd/oadp/restore_test.go|TestGenerateRestoreObjectBasic": {}, + "cmd/oadp/restore_test.go|TestGenerateRestoreObjectWithSchedule": {}, + "cmd/oadp/restore_test.go|TestValidateBackupExistsNotFound": {}, + "cmd/oadp/restore_test.go|TestValidateBackupExistsWithPhaseValidation": {}, + "cmd/oadp/schedule_test.go|TestBuildIncludedNamespacesForSchedule": {}, + "cmd/oadp/schedule_test.go|TestCreateScheduleOptionsDefaults": {}, + "cmd/oadp/schedule_test.go|TestGenerateScheduleObject": {}, + "cmd/oadp/schedule_test.go|TestGenerateScheduleObjectComprehensive": {}, + "cmd/oadp/schedule_test.go|TestGenerateScheduleObjectWithIncludedNamespaces": {}, + "cmd/oadp/schedule_test.go|TestGenerateScheduleObjectWithKubeVirtResourcePolicy": {}, + "cmd/oadp/schedule_test.go|TestScheduleCommandIncludedNamespacesFlag": {}, + "cmd/oadp/schedule_test.go|TestScheduleNameValidation": {}, + "contrib/aws-leaked-resources/delete_test.go|TestDeleter_ActiveInfraSetNeverDeleted": {}, + "contrib/aws-leaked-resources/delete_test.go|TestDeleter_CollectNATGatewayEIPsReturnsCorrectIDs": {}, + "contrib/aws-leaked-resources/delete_test.go|TestDeleter_DefaultSGNeverDeleted": {}, + "contrib/aws-leaked-resources/delete_test.go|TestDeleter_EIPRelease": {}, + "contrib/aws-leaked-resources/delete_test.go|TestDeleter_ELBOnlyDeletesTargetVPC": {}, + "contrib/aws-leaked-resources/delete_test.go|TestDeleter_FullCascadeOnlyTargetsOwnResources": {}, + "contrib/aws-leaked-resources/delete_test.go|TestDeleter_MainRTBNeverDeleted": {}, + "contrib/aws-leaked-resources/delete_test.go|TestDeleter_ProtectedInfraSetNeverDeleted": {}, + "contrib/aws-leaked-resources/delete_test.go|TestDeleter_RealWorldScenario_CI2EIPSurvives": {}, + "contrib/aws-leaked-resources/delete_test.go|TestDeleter_VPCEndpointServicesOnlyDeletesMatchingInfraID": {}, + "contrib/aws-leaked-resources/scan_test.go|TestCheckEC2Instances_APIError": {}, + "contrib/aws-leaked-resources/scan_test.go|TestFullScan_MixedVPCs": {}, + "contrib/aws-leaked-resources/scan_test.go|TestGate0_DoNotDeleteTag": {}, + "contrib/aws-leaked-resources/scan_test.go|TestGate0_ProtectedUsers": {}, + "contrib/aws-leaked-resources/scan_test.go|TestGate0_ProtectedVPCs": {}, + "contrib/aws-leaked-resources/scan_test.go|TestGate1_Age": {}, + "contrib/aws-leaked-resources/scan_test.go|TestGate1_AgeUnknown": {}, + "contrib/aws-leaked-resources/scan_test.go|TestGate1_ExpirationDate": {}, + "contrib/aws-leaked-resources/scan_test.go|TestGate3_OIDC": {}, + "contrib/aws-leaked-resources/scan_test.go|TestGate4_EC2Instances": {}, + "contrib/aws-leaked-resources/scan_test.go|TestUncertainVerdict": {}, + "control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller_test.go|TestHCPExternalNames": {}, + "control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller_test.go|TestReconcileDeletion": {}, + "control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller_test.go|TestReconcileDeletionSharedVPC": {}, + "control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller_test.go|TestReconcileDeletion_AfterControllerRestart": {}, + "control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller_test.go|TestRecordForService": {}, + "control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller_test.go|TestVPCEndpointPort": {}, + "control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller_test.go|TestVPCEndpointSecurityGroupFilter": {}, + "control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller_test.go|TestVPCEndpointSecurityGroupName": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestDNSZoneConfigErrMsgQualifier": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestEnsureHCPFinalizer_WhenAlreadyPresent_ItShouldNotModify": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestEnsureHCPFinalizer_WhenNotPresent_ItShouldAddFinalizer": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestEnsureHCPFinalizer_WhenPatchConflicts_ItShouldRequeue": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestErrMsgQualifier": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestExtractPrivateEndpointIP": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestExtractPrivateEndpointIP_WhenNICHasNilElements_ItShouldSkipThem": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestHandleAzureError_WhenStatusPatchFails_ItShouldReturnPatchError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestHcpExternalPrivateServices_WhenBothRouteHostnamesConfigured_ItShouldReturnBoth": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestHcpExternalPrivateServices_WhenNoRouteStrategy_ItShouldReturnEmpty": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestHcpExternalPrivateServices_WhenRouteHasNoHostname_ItShouldSkipIt": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestMapHCPToAzurePLS": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestMapHCPToAzurePLS_WhenObjectIsNotHCP_ItShouldReturnNil": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileBaseDomainDNS_WhenDNSZoneCreateFails_ItShouldRequeueAfterError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileBaseDomainDNS_WhenOAuthCR_ItShouldOnlyCreateOAuthRecord": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileBaseDomainDNS_WhenPrivateRouterWithNoSibling_ItShouldCreateBothRecords": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileBaseDomainDNS_WhenPrivateRouterWithSiblingOAuth_ItShouldOnlyCreateAPIRecord": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDNSZone_WhenStatusPatchFails_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDNS_WhenARecordCreateFails_ItShouldRequeueAfterError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDNS_WhenDNSZoneCreateFails_ItShouldRequeueAfterError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDNS_WhenDNSZonePollerFails_ItShouldRequeueAfterError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDNS_WhenDNSZonePollerSucceeds_ItShouldCreateLinkAndRecords": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDNS_WhenPEIPNotAvailable_ItShouldRequeue": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDNS_WhenVNetAlreadyLinked_ItShouldContinue": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDNS_WhenVNetLinkCreateFails_ItShouldRequeueAfterError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDNS_WhenVNetLinkPollerFails_ItShouldRequeueAfterError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenARecordDeleteReturnsNon404Error_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenAllDeletesReturn404_ItShouldCompleteSuccessfully": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenBaseDomainButClusterNameCannotBeDerived_ItShouldSkipRecordDeletion": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenBaseDomainPollersSucceed_ItShouldDeleteAllBaseDomainResources": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenBaseDomainRecordDeleteFails_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenBaseDomainVNetLinkDeleteFails_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenBaseDomainVNetLinkPollerFails_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenBaseDomainZoneDeleteFails_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenBaseDomainZonePollerFails_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenDNSZoneDeleteReturnsNon404Error_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenDNSZoneNameNotSet_ItShouldSkipDNSCleanup": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenDNSZonePollerFails_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenDeletePollersReturn404_ItShouldSucceed": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenNoSiblingCRs_ItShouldDeleteBaseDomainZone": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenNonPrivateRouterWithBaseDomain_ItShouldDeleteOAuthRecordOnly": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenPEDeleteReturnsNon404Error_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenPEPollerFails_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenPollersSucceed_ItShouldDeleteAllResources": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenSiblingCRsExist_ItShouldNotDeleteBaseDomainZone": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenVNetLinkDeleteReturnsNon404Error_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileDelete_WhenVNetLinkPollerFails_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileExternalService_ItShouldSetAllRequiredFields": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileExternalService_WhenServiceHasExistingLabelsAndAnnotations_ItShouldPreserveThem": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileExternalServices_WhenCreateOrUpdateFails_ItShouldReturnAggregateError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileExternalServices_WhenPrivateWithExternalNames_ItShouldCreateServices": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileExternalServices_WhenPrivateWithNoExternalNames_ItShouldNotCreateServices": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileExternalServices_WhenPublic_ItShouldDeleteExistingServices": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileHCPDeletion_WhenAzureCleanupFails_ItShouldReturnErrorAndPreserveFinalizer": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileHCPDeletion_WhenBaseDomainZoneDeletionFails_ItShouldPreserveFinalizers": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileHCPDeletion_WhenHCPDoesNotHaveFinalizer_ItShouldBeNoOp": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileHCPDeletion_WhenHCPIsBeingDeleted_ItShouldCleanUpAndRemoveFinalizer": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileHCPDeletion_WhenMultipleCRsExist_ItShouldRemoveAllPerCRFinalizers": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileHCPDeletion_WhenMultipleCRsShareBaseDomain_ItShouldDeleteBaseDomainZone": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileHCPDeletion_WhenPatchConflicts_ItShouldRequeue": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileHCPDeletion_WhenSiblingCleanupFails_ItShouldPreserveAllFinalizers": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcileHCPDeletion_WhenSingleCRHasBaseDomain_ItShouldDeleteBaseDomainZoneIdempotently": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcilePrivateEndpoint_WhenCreateFails_ItShouldSetErrorCondition": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcilePrivateEndpoint_WhenGetReturnsNon404Error_ItShouldSetErrorCondition": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcilePrivateEndpoint_WhenPEAlreadyExists_ItShouldUpdateStatus": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcilePrivateEndpoint_WhenPollerFails_ItShouldSetErrorCondition": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcilePrivateEndpoint_WhenPollerSucceeds_ItShouldUpdateStatusWithPEInfo": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenAllResourcesAreCreated_ItShouldSetAvailableCondition": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenAvailableConditionPatchFails_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenBaseDomainDNSFails_ItShouldReturnRequeueResult": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenCRAlreadyDeleted_ItShouldReturnNoError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenCRDeletionFails_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenCRDeletionSucceeds_ItShouldRemoveFinalizerAndCleanUp": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenCRDeletionWithFinalizer_ItShouldCleanUpAndRemoveFinalizer": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenCRIsDeleted_ItShouldCleanUpPEAndDNS": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenCRNotFound_ItShouldReturnNoError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenDNSReconcileFails_ItShouldReturnRequeueResult": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenFinalizerAddConflicts_ItShouldRequeue": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenFinalizerNotPresent_ItShouldAddFinalizer": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenGetHCPFails_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenHCPIsBeingDeleted_ItShouldNotReAddPerCRFinalizer": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenHCPIsBeingDeleted_ItShouldTriggerCleanupInsteadOfCreation": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenHCPIsGone_ItShouldRemoveOrphanedPerCRFinalizer": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenNonPrivateRouterCR_ItShouldSkipHypershiftLocalDNS": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenNonPrivateRouterDNSZoneNamePatchFails_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenNonPrivateRouterWithDNSZoneNameAlreadySet_ItShouldPreserveExistingDNSZoneName": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenPEConnectionNotApproved_ItShouldRequeueWithWarning": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenPEIsCreated_ItShouldCreateDNSZoneAndARecord": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenPEReconcileFails_ItShouldReturnRequeueResult": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenPLSAliasIsAvailable_ItShouldAddHCPFinalizer": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenPLSAliasIsAvailable_ItShouldCreatePrivateEndpoint": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenPLSAliasIsNotYetAvailable_ItShouldRequeue": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestReconcile_WhenPrivateRouterWithBaseDomain_ItShouldReconcileBaseDomainDNS": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestUpdatePrivateEndpointStatus_WhenConnectionPendingAndPatchFails_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestUpdatePrivateEndpointStatus_WhenStatusPatchFails_ItShouldReturnError": {}, + "control-plane-operator/controllers/azureprivatelinkservice/controller_test.go|TestVNetLinkName": {}, + "control-plane-operator/controllers/azureprivatelinkservice/observer_test.go|TestReconcile": {}, + "control-plane-operator/controllers/azureprivatelinkservice/observer_test.go|TestReconcile_WhenServiceNotFound_ItShouldReturnNoError": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/dns_test.go|TestGenerateZoneNames": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/dns_test.go|TestGenerateZoneNamesValidationErrors": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/dns_test.go|TestZoneNameLengthConstraints": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/observer_test.go|TestReconcileIntegration": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestConstructEndpointName": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestConstructIPAddressName": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestDNSEndpointNameTrimming": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestDNSEndpointNaming": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestEndpointNameUniqueness": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestHCPExternalNamesGCP": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestIPAddressNameUniqueness": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestNameserverTrailingDotTrimming": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestNamingFunctionConsistency": {}, + "control-plane-operator/controllers/healthcheck/aws_test.go|TestAWSHealthCheckIdentityProviderEC2ClientNil": {}, + "control-plane-operator/controllers/healthcheck/aws_test.go|TestAWSHealthCheckIdentityProviderWithMockEC2": {}, + "control-plane-operator/controllers/healthcheck/aws_test.go|TestClassifyIdentityProviderError": {}, + "control-plane-operator/controllers/healthcheck/aws_test.go|TestClassifyIdentityProviderErrorMessageStability": {}, + "control-plane-operator/controllers/healthcheck/aws_test.go|TestUpdateRunsAWSIdentityCheckDuringDeletion": {}, + "control-plane-operator/controllers/healthcheck/gcp_test.go|TestGCPHealthCheckConditionDifferentiation": {}, + "control-plane-operator/controllers/healthcheck/gcp_test.go|TestGCPHealthCheckIdentityProviderConditionLogic": {}, + "control-plane-operator/controllers/healthcheck/gcp_test.go|TestUpdateRunsGCPIdentityCheckDuringDeletion": {}, + "control-plane-operator/controllers/hostedcontrolplane/controlplaneversion_pruning_test.go|TestPruneHistory": {}, + "control-plane-operator/controllers/hostedcontrolplane/controlplaneversion_pruning_test.go|TestPruneHistory_CapAtMaxHistory": {}, + "control-plane-operator/controllers/hostedcontrolplane/controlplaneversion_pruning_test.go|TestPruneHistory_DeterministicResults": {}, + "control-plane-operator/controllers/hostedcontrolplane/controlplaneversion_pruning_test.go|TestPruneHistory_ExactlyMaxHistoryUnchanged": {}, + "control-plane-operator/controllers/hostedcontrolplane/controlplaneversion_pruning_test.go|TestPruneHistory_UnderMaxHistoryUnchanged": {}, + "control-plane-operator/controllers/hostedcontrolplane/controlplaneversion_test.go|TestReconcileControlPlaneVersion_AllComponentsComplete": {}, + "control-plane-operator/controllers/hostedcontrolplane/controlplaneversion_test.go|TestReconcileControlPlaneVersion_ComponentFailure": {}, + "control-plane-operator/controllers/hostedcontrolplane/controlplaneversion_test.go|TestReconcileControlPlaneVersion_ControlPlaneReleaseImage": {}, + "control-plane-operator/controllers/hostedcontrolplane/controlplaneversion_test.go|TestReconcileControlPlaneVersion_FirstPopulation": {}, + "control-plane-operator/controllers/hostedcontrolplane/controlplaneversion_test.go|TestReconcileControlPlaneVersion_ImageOnlyChange": {}, + "control-plane-operator/controllers/hostedcontrolplane/controlplaneversion_test.go|TestReconcileControlPlaneVersion_NewComponentMidUpgrade": {}, + "control-plane-operator/controllers/hostedcontrolplane/controlplaneversion_test.go|TestReconcileControlPlaneVersion_NewDesiredRelease": {}, + "control-plane-operator/controllers/hostedcontrolplane/controlplaneversion_test.go|TestReconcileControlPlaneVersion_NoComponents": {}, + "control-plane-operator/controllers/hostedcontrolplane/controlplaneversion_test.go|TestReconcileControlPlaneVersion_ObservedGeneration": {}, + "control-plane-operator/controllers/hostedcontrolplane/controlplaneversion_test.go|TestReconcileControlPlaneVersion_SupersededPartial": {}, + "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestAWSSecurityGroupTags": {}, + "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestControlPlaneComponents": {}, + "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestEventHandling": {}, + "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestNonReadyInfraTriggersRequeueAfter": {}, + "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestReconcileDefaultSecurityGroup": {}, + "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestReconcileDefaultSecurityGroup_GenerationConflict": {}, + "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestReconcileIgnitionServer": {}, + "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestRouterComponentComesAfterRouteCreatingComponents": {}, + "control-plane-operator/controllers/hostedcontrolplane/infra/infra_test.go|TestReconcileInfrastructure": {}, + "control-plane-operator/controllers/hostedcontrolplane/infra/infra_test.go|TestReconcileInfrastructure_ErrorCases": {}, + "control-plane-operator/controllers/hostedcontrolplane/infra/infra_test.go|TestReconcileInfrastructure_WhenTransitioningFromPrivateToPublic_ItShouldCleanUpPrivateResources": {}, + "control-plane-operator/controllers/hostedcontrolplane/infra/infra_test.go|TestReconcileInfrastructure_WhenTransitioningFromPublicToPrivate_ItShouldCleanUpPublicResources": {}, + "control-plane-operator/controllers/hostedcontrolplane/infra/infra_test.go|TestReconcileOAuthService": {}, + "control-plane-operator/controllers/hostedcontrolplane/infra/infra_test.go|TestReconcileOAuthService_AzureLoadBalancer": {}, + "control-plane-operator/controllers/hostedcontrolplane/ingress/router_test.go|TestReconcileRouterServiceAnnotations": {}, + "control-plane-operator/controllers/hostedcontrolplane/ingress/router_test.go|TestReconcileRouterService_AppliesLoadBalancerSourceRanges": {}, + "control-plane-operator/controllers/hostedcontrolplane/ingress/router_test.go|TestReconcileRouterService_GCPInternalLoadBalancer": {}, + "control-plane-operator/controllers/hostedcontrolplane/ingress/router_test.go|TestReconcileRouterService_WhenExternalAWS_ItShouldSetInternetFacingScheme": {}, + "control-plane-operator/controllers/hostedcontrolplane/ingress/router_test.go|TestReconcileRouterService_WhenSwitchingMode_ItShouldRemoveConflictingAnnotation": {}, + "control-plane-operator/controllers/hostedcontrolplane/kas/healthcheck_test.go|TestGetHealthcheckEndpoint": {}, + "control-plane-operator/controllers/hostedcontrolplane/kas/service_test.go|TestKonnectivityServiceReconcile": {}, + "control-plane-operator/controllers/hostedcontrolplane/kas/service_test.go|TestReconcilePrivateService": {}, + "control-plane-operator/controllers/hostedcontrolplane/kas/service_test.go|TestReconcilePrivateServiceAWSNLBAnnotation": {}, + "control-plane-operator/controllers/hostedcontrolplane/kas/service_test.go|TestReconcileService": {}, + "control-plane-operator/controllers/hostedcontrolplane/kas/service_test.go|TestReconcileServiceAzureInternalLB": {}, + "control-plane-operator/controllers/hostedcontrolplane/manifests/etcd_test.go|TestEtcdBackupJobManifests": {}, + "control-plane-operator/controllers/hostedcontrolplane/oauth/idp_convert_test.go|TestConvertIdentityProviders_ErrorWrapping": {}, + "control-plane-operator/controllers/hostedcontrolplane/oauth/idp_convert_test.go|TestConvertProviderConfigToIDPData_Routing": {}, + "control-plane-operator/controllers/hostedcontrolplane/oauth/idp_convert_test.go|TestConvertProviderConfigToIDPData_UnsupportedType": {}, + "control-plane-operator/controllers/hostedcontrolplane/oauth/idp_convert_test.go|TestOpenIDProviderConversion": {}, + "control-plane-operator/controllers/hostedcontrolplane/oauth/service_test.go|TestOauthServiceReconcile": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/azure_workload_identity_webhook_test.go|TestReconcileAzureWorkloadIdentityWebhookServingCert": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/azure_workload_identity_webhook_test.go|TestReconcileAzureWorkloadIdentityWebhookServingCertIdempotent": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/konnectivity_test.go|TestKonnectivitySeparateSigners": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/assets/assets_test.go|TestForEachManifest": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/assets/assets_test.go|TestForEachManifestWithActionError": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/assets/assets_test.go|TestKonnectivityServerAuthenticatesAgents": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/assets/assets_test.go|TestLoadDeploymentManifest": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/autoscaler/component_test.go|TestAdaptDeployment": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/autoscaler/component_test.go|TestAdaptDeploymentWithClusterAutoscalerImage": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/capi_manager/deployment_test.go|TestAdaptDeployment": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/capi_manager/deployment_test.go|TestAdaptDeployment_ParseVersionError": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/capi_provider/deployment_test.go|TestAdaptDeployment": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/capi_provider/deployment_test.go|TestAdaptDeployment_WithNilAnnotations": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/capi_provider/deployment_test.go|TestAdaptDeployment_WithProxyEnvVars": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/capi_provider/role_test.go|TestAdaptRole": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/capi_provider/role_test.go|TestAdaptRole_WithNilAnnotations": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/aws/config_test.go|TestConfig": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/aws/config_test.go|TestConfigErrorStates": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/aws/deployment_test.go|TestAdaptDeployment": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/aws/deployment_test.go|TestAdaptDeploymentTLS": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/azure/config_test.go|TestConfig": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/azure/config_test.go|TestConfigErrorStates": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/azure/config_test.go|TestConfigSecretDoesNotContainAcrMI": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/gcp/config_test.go|TestConfig": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/gcp/config_test.go|TestConfigErrorStates": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/kubevirt/config_test.go|TestConfig": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/powervs/component_test.go|TestPowerVSOptions": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/powervs/config_test.go|TestAdaptConfig": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/powervs/config_test.go|TestAdaptConfigErrorStates": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/powervs/config_test.go|TestAdaptConfigMapDataStructure": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/powervs/config_test.go|TestAdaptConfigTemplateExecution": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/powervs/deployment_test.go|TestAdaptDeployment": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/powervs/deployment_test.go|TestAdaptDeploymentErrorStates": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/powervs/deployment_test.go|TestAdaptDeploymentTLSArgs": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/powervs/deployment_test.go|TestAdaptDeploymentWithAssets": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_credential_operator/component_test.go|TestCloudCredentialOperatorOptions": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_credential_operator/deployment_test.go|TestAdaptDeployment": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_credential_operator/deployment_test.go|TestAdaptDeploymentUpdatesContainer": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/controlplaneoperator/deployment_test.go|TestAdaptDeploymentImageOverrides": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/controlplaneoperator/deployment_test.go|TestAdaptDeploymentWatchListClientEnv": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/csi/kubevirt/config_test.go|TestAdaptConfigMapConsistentOrdering": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/csi/kubevirt/config_test.go|TestAdaptConfigMapEmptyMappings": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/dnsoperator/component_test.go|TestComponentOptions": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/etcd/etcd_test.go|TestAdaptStatefulSet": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/etcd/etcd_test.go|TestAdaptStatefulSetEtcdLogLevel": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/etcd/shard_test.go|TestAdaptStatefulSetForShard": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/etcd/shard_test.go|TestAdaptStatefulSetForShard_IPv6WithDefrag": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/etcd/shard_test.go|TestEtcdShardInterface": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/fg/job_test.go|TestAdaptJob": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/fg/job_test.go|TestAdaptJobPreservesExistingEnvVars": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/ignitionserver/route_test.go|TestIgnitionRouteAdapt": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/ignitionserver/service_test.go|TestIgnitionServiceReconcile": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/ignitionserver_proxy/deployment_test.go|TestAdaptDeployment": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/ignitionserver_proxy/deployment_test.go|TestAdaptDeploymentWithProxyEnvVars": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/ignitionserver_proxy/deployment_test.go|TestAdaptHAProxyConfig": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/ignitionserver_proxy/deployment_test.go|TestAdaptHAProxyConfigErrors": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/ignitionserver_proxy/service_test.go|TestAdaptService": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/ignitionserver_proxy/service_test.go|TestAdaptServiceMultiplePorts": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/ignitionserver_proxy/service_test.go|TestAdaptServicePreservesNodePort": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/ignitionserver_proxy/service_test.go|TestAdaptServiceValidatesStrategyType": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/ignitionserver_proxy/service_test.go|TestServicePublishingStrategyByTypeForHCP": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/karpenteroperator/component_test.go|TestKarpenterCredentialsSecretReconcile": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/deployment_test.go|TestAdaptDeploymentKASLogLevel": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/deployment_test.go|TestKonnectivityServerTLSMinVersion": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go|TestGenerateKMSEncryptionConfig_Azure": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go|TestGenerateKMSPodConfig_ActiveContainerArgs": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go|TestGenerateKMSPodConfig_BackupContainerArgs": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go|TestGenerateKMSPodConfig_BackupKey": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go|TestGenerateKMSPodConfig_ContainerPorts": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go|TestGenerateKMSPodConfig_ImagePullPolicy": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go|TestGenerateKMSPodConfig_KASContainerMutation": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go|TestGenerateKMSPodConfig_LivenessProbe": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go|TestGenerateKMSPodConfig_Managed": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go|TestGenerateKMSPodConfig_NoBackupContainerWithoutBackupKey": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go|TestGenerateKMSPodConfig_ResourceRequests": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go|TestGenerateKMSPodConfig_SelfManaged": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go|TestGenerateKMSPodConfig_VolumeMountPaths": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kubeconfig_test.go|TestGenerateKubeConfigWithServingCerts": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kubeconfig_test.go|TestGenerateKubeConfigWithServingCerts_WhenClientCertIsMissing": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kubeconfig_test.go|TestGenerateKubeConfigWithServingCerts_WhenRootCAIsMissing": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/params_test.go|TestNewAPIServerParamsAPIAdvertiseAddressAndPort": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/params_test.go|TestNewConfigParams": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/secretencryption_test.go|TestReconcileKMSEncryptionConfigAWS": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/secretencryption_test.go|TestReconcileKMSEncryptionConfigAzure": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/secretencryption_test.go|TestReconcileKMSEncryptionConfigAzureSelfManaged": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kcm/component_test.go|TestKubeControllerManagerOptions": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kcm/config_test.go|TestGetServiceServingCA": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kcm/config_test.go|TestGetServiceServingCAError": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kcm/deployment_test.go|TestAdaptDeployment": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kcm/deployment_test.go|TestAdaptDeploymentKCMLogLevel": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kube_scheduler/component_test.go|TestKubeSchedulerOptions": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kube_scheduler/config_test.go|TestGenerateConfig": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kube_scheduler/deployment_test.go|TestAdaptDeploymentSchedulerLogLevel": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/machine_approver/component_test.go|TestPredicate": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/machine_approver/component_test.go|TestPredicateError": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/metrics_proxy/deployment_test.go|TestAdaptDeployment_MetricsSetOverride": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/metrics_proxy/scrape_config_test.go|TestAdaptScrapeConfig": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/metrics_proxy/scrape_config_test.go|TestAdaptScrapeConfigMissingService": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/metrics_proxy/scrape_config_test.go|TestAdaptScrapeConfigMixedMonitors": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/metrics_proxy/scrape_config_test.go|TestAdaptScrapeConfigNamedTargetPort": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/metrics_proxy/scrape_config_test.go|TestAdaptScrapeConfigPodMonitorMissingPod": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/metrics_proxy/scrape_config_test.go|TestAdaptScrapeConfigPodMonitorNameMismatch": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/metrics_proxy/scrape_config_test.go|TestAdaptScrapeConfigWithPodMonitors": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/metrics_proxy/scrape_config_test.go|TestComponentFromPodMonitor_EdgeCases": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/metrics_proxy/scrape_config_test.go|TestComponentFromServiceMonitor_EdgeCases": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/nto/component_test.go|TestClusterNodeTuningOperatorOptions": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/nto/deployment_test.go|TestAdaptDeployment": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/nto/deployment_test.go|TestAdaptDeploymentUpdatesExistingEnvVars": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/nto/servicemonitor_test.go|TestAdaptServiceMonitor": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/nto/servicemonitor_test.go|TestAdaptServiceMonitorErrorCases": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/nto/servicemonitor_test.go|TestAdaptServiceMonitorMetricsSetConfigurations": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/oapi/deployment_test.go|TestAdaptDeploymentOAPILogLevel": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/oapi/deployment_test.go|TestReconcileOpenshiftAPIServerDeploymentTrustBundle": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/oauth/deployment_test.go|TestAdaptDeployment": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/oauth/deployment_test.go|TestAdaptDeploymentOAuthLogLevel": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/oauth/idp_convert_test.go|TestConvertIdentityProviders_ErrorWrapping": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/oauth/idp_convert_test.go|TestConvertProviderConfigToIDPData_Routing": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/oauth/idp_convert_test.go|TestConvertProviderConfigToIDPData_UnsupportedType": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/oauth/idp_convert_test.go|TestOpenIDProviderConversion": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/oauth_apiserver/component_test.go|TestOpenshiftOAuthAPIServerOptions": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/oauth_apiserver/deployment_test.go|TestAdaptDeployment": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/oauth_apiserver/deployment_test.go|TestAdaptDeploymentMultipleConfigurations": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/oauth_apiserver/deployment_test.go|TestAdaptDeploymentOAuthAPIServerLogLevel": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/oauth_apiserver/deployment_test.go|TestAdaptDeploymentWithInvalidEtcdURL": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/ocm/config_test.go|TestAdaptConfig_DisabledImageRegistryCapability": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/ocm/config_test.go|TestAdaptConfig_PreservesExistingControllers": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/ocm/config_test.go|TestReconcileOpenShiftControllerManagerConfig": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/ocm/deployment_test.go|TestAdaptDeploymentOCMLogLevel": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/olm/catalogs/deployment_test.go|TestCheckCatalogImageOverrides": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/olm/collect_profiles/cronjob_test.go|TestGenerateModularDailyCronSchedule": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/olm/collect_profiles/cronjob_test.go|TestGenerateModularDailyCronScheduleProperties": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/pkioperator/component_test.go|TestPKIOperatorOptions": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/pkioperator/deployment_test.go|TestAdaptDeployment": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/pkioperator/deployment_test.go|TestAdaptDeploymentCombinedEnvVars": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/pkioperator/deployment_test.go|TestAdaptDeploymentReturnsNoError": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/routecm/config_test.go|TestReconcileOpenShiftRouteControllerManagerConfig": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/router/util/util_test.go|TestUseHCPRouter": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/router/util/util_test.go|TestUseHCPRouterAroHCP": {}, + "control-plane-operator/endpoint-resolver/server_test.go|TestResolverHandler": {}, + "control-plane-operator/endpoint-resolver/server_test.go|TestResolverHandlerEmptySelector": {}, + "control-plane-operator/endpoint-resolver/server_test.go|TestResolverHandlerInvalidBody": {}, + "control-plane-operator/endpoint-resolver/server_test.go|TestResolverHandlerMethodNotAllowed": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/hcpstatus/hcpstatus_test.go|TestHCPStatusReconciler": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/inplaceupgrader/inplaceupgrader_test.go|TestReconcileInPlaceUpgradeAnnotatesMachineWithNodePoolVersion": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/inplaceupgrader/inplaceupgrader_test.go|TestReconcileInPlaceUpgradeDegradedNodeErrorMessage": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/inplaceupgrader/inplaceupgrader_test.go|TestReconcileReturnsRequeueAfterDuringUpgrade": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/inplaceupgrader/inplaceupgrader_test.go|TestReconcileUpgradePods": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/inplaceupgrader/inplaceupgrader_test.go|TestReconcileUpgradePodsDeleteNotFoundContinues": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/inplaceupgrader/inplaceupgrader_test.go|TestReconcileUpgradePodsMultiNodeMixedStates": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/inplaceupgrader/inplaceupgrader_test.go|TestReconcileUpgradePodsReturnsErrorWhenDeleteFails": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/machine/machine_test.go|TestReconcileDefaultIngressEndpoints": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/reencryption/reencryption_test.go|TestReconcile": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/reencryption/reencryption_test.go|TestReconcile_PatchStatusError": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/azure_workload_identity_webhook_test.go|TestReconcileAzureIdentityWebhook": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/azure_workload_identity_webhook_test.go|TestReconcileAzureIdentityWebhookIdempotent": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/gcp/gcp_test.go|TestReconcileGCPCredentials_EmptySecretKey": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/gcp/gcp_test.go|TestSetupOperandCredentials": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/gcp/gcp_test.go|TestSetupOperandCredentials_Storage": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/ingress/reconcile_test.go|TestConfigurationPriority": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/ingress/reconcile_test.go|TestReconcileDefaultIngressController": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/ingress/reconcile_test.go|TestReconcileDefaultIngressControllerWithCustomEndpointPublishingStrategy": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/monitoring/metricsforwarder_test.go|TestReconcileMetricsForwarderDeployment": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/monitoring/metricsforwarder_test.go|TestReconcileMetricsForwarderDeployment_ConfigHashChangeTrigersRollout": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/network/reconcile_test.go|TestReconcileDefaultIngressController": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestConnectionErrorTracking": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestDestroyCloudResources": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestDestroyCloudResourcesWithKASUnavailable": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestReconcileClusterVersion": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestReconcileClusterVersionWhenGuestCVOHasOlderCapabilities": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestReconcileClusterVersionWithDisabledCapabilities": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestReconcileClusterVersionWithEnabledCapabilities": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestReconcileErrorHandling": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestReconcileIngressControllerCertSource": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestReconcileRegistryAndIngress_ServiceAccountPullSecretsController": {}, + "control-plane-operator/metrics-proxy/filter_test.go|TestFilter_Apply": {}, + "control-plane-operator/metrics-proxy/filter_test.go|TestFilter_Apply_ConcurrentAccess": {}, + "control-plane-operator/metrics-proxy/filter_test.go|TestFilter_getOrCompile": {}, + "control-plane-operator/metrics-proxy/filter_test.go|TestFilter_getOrCompile_HandlesEmptyRegex": {}, + "control-plane-operator/metrics-proxy/filter_test.go|TestGetKeepRegexForComponent": {}, + "control-plane-operator/metrics-proxy/filter_test.go|TestGetKeepRegexForComponent_ValidRegex": {}, + "control-plane-pki-operator/certificaterevocationcontroller/certificaterevocationcontroller_test.go|TestCertificateRevocationController_processCertificateRevocationRequest": {}, + "control-plane-pki-operator/certificaterevocationcontroller/certificaterevocationcontroller_test.go|TestEnsureNewSignerCertificatePropagated_KASVerification": {}, + "control-plane-pki-operator/certificaterevocationcontroller/certificaterevocationcontroller_test.go|TestEnsureOldSignerCertificateRevoked_KASVerification": {}, + "control-plane-pki-operator/certificaterevocationcontroller/certificaterevocationcontroller_test.go|TestSyncCertificateRevocationRequest_RequeueBehavior": {}, + "control-plane-pki-operator/certificatesigningcontroller/certificatesigningcontroller_test.go|TestCertificateSigningController_processCertificateSigningRequest": {}, + "control-plane-pki-operator/certificatesigningcontroller/certificatesigningcontroller_test.go|TestSign": {}, + "control-plane-pki-operator/certificatesigningcontroller/certificatesigningcontroller_test.go|TestSign_WhenCSRSignatureIsCorrupted_ItShouldReturnAnError": {}, + "control-plane-pki-operator/certificatesigningrequestapprovalcontroller/certificatesigningrequestapprovalcontroller_test.go|TestCertificateSigningRequestApprovalController_processCertificateSigningRequest": {}, + "etcd-backup/etcdbackup_test.go|TestNewStartCommand": {}, + "etcd-backup/etcdbackup_test.go|TestNewStartCommandDefaults": {}, + "etcd-upload/azure_uploader_test.go|TestAzureBlobUploader": {}, + "etcd-upload/azure_uploader_test.go|TestAzureCredential": {}, + "etcd-upload/s3_uploader_test.go|TestS3Uploader": {}, + "hypershift-operator/controllers/auditlogpersistence/snapshot_controller_test.go|TestSnapshotReconciler_createSnapshot": {}, + "hypershift-operator/controllers/auditlogpersistence/snapshot_controller_test.go|TestSnapshotReconciler_manageRetention": {}, + "hypershift-operator/controllers/etcdbackup/credentials_test.go|TestCredentialBehavioralMethods": {}, + "hypershift-operator/controllers/etcdbackup/reconciler_test.go|TestCleanupResources": {}, + "hypershift-operator/controllers/etcdbackup/reconciler_test.go|TestCleanupResourcesWithDeleteErrors": {}, + "hypershift-operator/controllers/etcdbackup/reconciler_test.go|TestEnforceRetention": {}, + "hypershift-operator/controllers/etcdbackup/reconciler_test.go|TestEnforceRetentionDeleteRace": {}, + "hypershift-operator/controllers/etcdbackup/reconciler_test.go|TestEnsureRBAC": {}, + "hypershift-operator/controllers/etcdbackup/reconciler_test.go|TestEnsureRBACIdempotency": {}, + "hypershift-operator/controllers/etcdbackup/reconciler_test.go|TestEnsureServiceAccountWithCredentials": {}, + "hypershift-operator/controllers/etcdbackup/reconciler_test.go|TestHandleJobStatus": {}, + "hypershift-operator/controllers/etcdbackup/reconciler_test.go|TestHandleJobStatusEdgeCases": {}, + "hypershift-operator/controllers/etcdbackup/reconciler_test.go|TestReconcile": {}, + "hypershift-operator/controllers/etcdbackup/reconciler_test.go|TestReconcileHappyPath": {}, + "hypershift-operator/controllers/etcdbackup/reconciler_test.go|TestReconcileHappyPathWithAzureWI": {}, + "hypershift-operator/controllers/etcdbackup/reconciler_test.go|TestReconcileHappyPathWithSTS": {}, + "hypershift-operator/controllers/etcdbackup/reconciler_test.go|TestReconcileMonitorsExistingJob": {}, + "hypershift-operator/controllers/etcdbackup/reconciler_test.go|TestReconcileRecoversWhenCredentialSecretAppears": {}, + "hypershift-operator/controllers/etcdbackup/reconciler_test.go|TestReconcileTerminalCleansUpAndEnforcesRetention": {}, + "hypershift-operator/controllers/hostedcluster/enqueue_secret_test.go|TestEnqueueHostedClustersForReferencedSecret": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestDeleteOrcImagesDuringHostedClusterDeletion": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestDestroyGracePeriod": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestHasBeenAvailable": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestHostedClusterWatchesEverythingItCreates": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestIsUpgradeable": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestKubevirtETCDEncKey": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestReconcileCLISecrets": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestReconcileCLISecretsErrors": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestReconcileComponents": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestReconcileHostedControlPlaneAPINetwork": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestReconcileHostedControlPlaneAdditionalTrustBundle": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestReconcileHostedControlPlaneConfiguration": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestReconcileHostedControlPlaneLabelSync": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestReconcileHostedControlPlaneMonitoring": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestReconcileHostedControlPlaneUpgrades": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestReconcileKubeconfigAndPasswordSync_WhenKubeconfigFails_ItShouldStillSyncKubeadminPassword": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestReconcileNonBlockingBehavior": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestReconcileRBACAndPolicies_WhenPKIRBACFails_ItShouldStillCreatePrometheusRBAC": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestReconcileSREMetricsConfig_EffectiveMetricsSet": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestReconcileWhenPullSecretMissingAndNodePoolListFails": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestReconcileWhenPullSecretUnavailable": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestReconciliationSuccessConditionSetting": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_webhook_test.go|TestKubevirtClusterServiceDefaulting": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_webhook_test.go|TestKubevirtNodePoolManagementDefaulting": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_webhook_test.go|TestValidateKVHostedClusterCreate": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_webhook_test.go|TestValidateKVHostedClusterUpdate": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_webhook_test.go|TestValidateKVNodePoolCreate": {}, + "hypershift-operator/controllers/hostedcluster/hostedcluster_webhook_test.go|TestValidateKVNodePoolUpdate": {}, + "hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure_test.go|TestDeleteOrphanedMachines": {}, + "hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure_test.go|TestDeleteOrphanedMachines_CapiProviderLookupError": {}, + "hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_conditions_test.go|TestNetworkConfigAccessSafety": {}, + "hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_conditions_test.go|TestWorkloadIdentityValidationScenarios": {}, + "hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_test.go|TestCAPIProviderDeploymentSpec": {}, + "hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_test.go|TestCAPIProviderDeploymentSpecNilGCPPlatform": {}, + "hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_test.go|TestCAPIProviderDeploymentSpecWithTLS": {}, + "hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_test.go|TestGCPPlatformInterface": {}, + "hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_test.go|TestReconcileCredentials": {}, + "hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_test.go|TestReconcileCredentialsNilGCPPlatform": {}, + "hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_test.go|TestReconcileGCPClusterPreservesServerDefaultedFields": {}, + "hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_test.go|TestValidateWorkloadIdentityConfiguration": {}, + "hypershift-operator/controllers/hostedcluster/internal/platform/openstack/openstack_test.go|TestCAPIProviderDeploymentSpec": {}, + "hypershift-operator/controllers/hostedcluster/internal/platform/openstack/openstack_test.go|TestCAPIProviderDeploymentSpecWithTLS": {}, + "hypershift-operator/controllers/hostedcluster/internal/platform/openstack/openstack_test.go|TestReconcileOpenStackCluster": {}, + "hypershift-operator/controllers/hostedcluster/metrics/metrics_test.go|TestReportTransitionDurationForAWSEndpointConditions": {}, + "hypershift-operator/controllers/hostedcluster/metrics/metrics_test.go|TestReportTransitionDurationForGCPEndpointConditions": {}, + "hypershift-operator/controllers/hostedcluster/network_policies_test.go|TestGCPPrivateRouterNetworkPolicy_IngressOnly": {}, + "hypershift-operator/controllers/hostedcluster/network_policies_test.go|TestKasEndpointsToCIDRs": {}, + "hypershift-operator/controllers/hostedcluster/network_policies_test.go|TestReconcileNetworkPolicies_GCP_PrivateRouter": {}, + "hypershift-operator/controllers/hostedcluster/network_policies_test.go|TestReconcileNetworkPolicies_HCPEgressBlockCIDRs": {}, + "hypershift-operator/controllers/hostedcluster/network_policies_test.go|TestReconcileNetworkPolicies_LoadBalancerOauth": {}, + "hypershift-operator/controllers/hostedcluster/network_policies_test.go|TestReconcileNetworkPolicies_OpenshiftIngressPolicy": {}, + "hypershift-operator/controllers/hostedcluster/reconcile_report_test.go|TestBlockingBehavior": {}, + "hypershift-operator/controllers/hostedcluster/reconcile_report_test.go|TestExecuteOrBlock": {}, + "hypershift-operator/controllers/hostedcluster/reconcile_report_test.go|TestReconcileReport": {}, + "hypershift-operator/controllers/hostedcluster/reconcile_report_tracing_test.go|TestReconcileReportBlockedOperationMarksSpan": {}, + "hypershift-operator/controllers/hostedcluster/reconcile_report_tracing_test.go|TestReconcileReportExecuteCreatesSpan": {}, + "hypershift-operator/controllers/hostedcluster/reconcile_report_tracing_test.go|TestReconcileReportExecuteOrBlockCreatesSpan": {}, + "hypershift-operator/controllers/hostedcluster/reconcile_report_tracing_test.go|TestReconcileReportExecuteRecordsError": {}, + "hypershift-operator/controllers/hostedcluster/reconcile_report_tracing_test.go|TestReconcileReportMultipleOperationsCreateMultipleSpans": {}, + "hypershift-operator/controllers/hostedcluster/tracing_test.go|TestReconcileTracingDeletingCluster": {}, + "hypershift-operator/controllers/hostedcluster/tracing_test.go|TestReconcileTracingNoClusterIDOmitsAttribute": {}, + "hypershift-operator/controllers/hostedcluster/tracing_test.go|TestReconcileTracingNoSpanLinkWithoutTraceparent": {}, + "hypershift-operator/controllers/hostedcluster/tracing_test.go|TestReconcileTracingNotFoundSkipsSpan": {}, + "hypershift-operator/controllers/hostedcluster/tracing_test.go|TestReconcileTracingRecordError": {}, + "hypershift-operator/controllers/hostedcluster/tracing_test.go|TestReconcileTracingSpanAttributes": {}, + "hypershift-operator/controllers/hostedcluster/tracing_test.go|TestReconcileTracingSpanLinkFromTraceparent": {}, + "hypershift-operator/controllers/hostedclustersizing/hostedclustersizing_controller_test.go|TestSizingController_Reconcile": {}, + "hypershift-operator/controllers/manifests/controlplanepkioperator/reconcile_test.go|TestReconcileCSRApproverClusterRole": {}, + "hypershift-operator/controllers/manifests/controlplanepkioperator/reconcile_test.go|TestReconcileCSRApproverClusterRoleBinding": {}, + "hypershift-operator/controllers/manifests/controlplanepkioperator/reconcile_test.go|TestReconcileCSRSignerClusterRole": {}, + "hypershift-operator/controllers/manifests/controlplanepkioperator/reconcile_test.go|TestReconcileCSRSignerClusterRoleBinding": {}, + "hypershift-operator/controllers/nodepool/apiserver-haproxy/haproxy_test.go|TestAPIServerHAProxyConfig": {}, + "hypershift-operator/controllers/nodepool/aws_test.go|TestAWSMachineTemplate": {}, + "hypershift-operator/controllers/nodepool/aws_test.go|TestAWSMachineTemplateSpec": {}, + "hypershift-operator/controllers/nodepool/aws_test.go|TestAWSMachineTemplateSpec_StreamSelection": {}, + "hypershift-operator/controllers/nodepool/aws_test.go|TestAWSTagConflicts": {}, + "hypershift-operator/controllers/nodepool/aws_test.go|TestApplyAWSMachineOptions": {}, + "hypershift-operator/controllers/nodepool/capi_test.go|TestCAPIReconcile": {}, + "hypershift-operator/controllers/nodepool/capi_test.go|TestCAPIReconcile_machineset": {}, + "hypershift-operator/controllers/nodepool/capi_test.go|TestGlobalPSManagedLabelOnMachines": {}, + "hypershift-operator/controllers/nodepool/capi_test.go|TestInPlaceUpgradeMaxUnavailable": {}, + "hypershift-operator/controllers/nodepool/capi_test.go|TestListMachineTemplatesAWS": {}, + "hypershift-operator/controllers/nodepool/capi_test.go|TestListMachineTemplatesIBMCloud": {}, + "hypershift-operator/controllers/nodepool/capi_test.go|TestMHCRemediationAllowedBubbledUpToReady": {}, + "hypershift-operator/controllers/nodepool/capi_test.go|TestMHCRemediationAllowedChangedPredicate": {}, + "hypershift-operator/controllers/nodepool/capi_test.go|TestMachineTemplateBuilders": {}, + "hypershift-operator/controllers/nodepool/capi_test.go|TestMachineTemplateBuildersPreexisting": {}, + "hypershift-operator/controllers/nodepool/capi_test.go|TestPause": {}, + "hypershift-operator/controllers/nodepool/capi_test.go|TestPauseUnpauseCycle": {}, + "hypershift-operator/controllers/nodepool/capi_test.go|TestReconcileMachineHealthCheck": {}, + "hypershift-operator/controllers/nodepool/capi_test.go|TestReconcileMachineHealthCheckAnnotation": {}, + "hypershift-operator/controllers/nodepool/conditions_test.go|TestGenerateReconciliationPausedCondition": {}, + "hypershift-operator/controllers/nodepool/config_test.go|TestCloudConfigHash": {}, + "hypershift-operator/controllers/nodepool/config_test.go|TestMissingCoreConfigError": {}, + "hypershift-operator/controllers/nodepool/gcp_test.go|TestGcpMachineTemplateSpec": {}, + "hypershift-operator/controllers/nodepool/gcp_test.go|TestGcpMachineTemplateSpecWithRHELStream": {}, + "hypershift-operator/controllers/nodepool/instancetype/aws/provider_test.go|TestTransformInstanceTypeInfoWhenMissingRequiredFieldsItShouldReturnError": {}, + "hypershift-operator/controllers/nodepool/instancetype/aws/provider_test.go|TestTransformInstanceTypeInfoWhenValidInputItShouldTransformCorrectly": {}, + "hypershift-operator/controllers/nodepool/instancetype/azure/provider_test.go|TestCacheTTLRefresh": {}, + "hypershift-operator/controllers/nodepool/instancetype/azure/provider_test.go|TestGetInstanceTypeInfo": {}, + "hypershift-operator/controllers/nodepool/instancetype/azure/provider_test.go|TestTransformSKU_WhenMissingRequiredFields_ItShouldReturnError": {}, + "hypershift-operator/controllers/nodepool/instancetype/azure/provider_test.go|TestTransformSKU_WhenValidInput_ItShouldTransformCorrectly": {}, + "hypershift-operator/controllers/nodepool/instancetype/azure/provider_test.go|TestVMSizeNotFoundError": {}, + "hypershift-operator/controllers/nodepool/kubevirt/kubevirt_test.go|TestJsonPatch": {}, + "hypershift-operator/controllers/nodepool/kubevirt/kubevirt_test.go|TestKubevirtMachineTemplate": {}, + "hypershift-operator/controllers/nodepool/metrics/metrics_test.go|TestCollect": {}, + "hypershift-operator/controllers/nodepool/metrics/metrics_test.go|TestCollectConcurrency": {}, + "hypershift-operator/controllers/nodepool/metrics/metrics_test.go|TestErrorCache": {}, + "hypershift-operator/controllers/nodepool/metrics/metrics_test.go|TestReportVCpusCountByHCluster": {}, + "hypershift-operator/controllers/nodepool/metrics/metrics_test.go|TestReportVCpusWithKarpenterAutoNode": {}, + "hypershift-operator/controllers/nodepool/nodepool_controller_test.go|TestGetName": {}, + "hypershift-operator/controllers/nodepool/nodepool_controller_test.go|TestNodePoolReconciler_reconcile": {}, + "hypershift-operator/controllers/nodepool/nodepool_controller_test.go|TestNodepoolDeletionDoesntRequireHCluster": {}, + "hypershift-operator/controllers/nodepool/nodepool_controller_test.go|TestSetCIDRConflictConditionDualStack": {}, + "hypershift-operator/controllers/nodepool/nto_test.go|TestSetPerformanceProfileStatus": {}, + "hypershift-operator/controllers/nodepool/openstack/openstack_test.go|TestClusterImageName": {}, + "hypershift-operator/controllers/nodepool/openstack/openstack_test.go|TestOpenStackMachineTemplate": {}, + "hypershift-operator/controllers/nodepool/token_test.go|TestOutdatedUserdataSecret": {}, + "hypershift-operator/controllers/nodepool/token_test.go|TestTokenUserDataSecret": {}, + "hypershift-operator/controllers/nodepool/tracing_test.go|TestNodePoolReconcileTracingDeletingNodePool": {}, + "hypershift-operator/controllers/nodepool/tracing_test.go|TestNodePoolReconcileTracingNotFoundSkipsSpan": {}, + "hypershift-operator/controllers/nodepool/tracing_test.go|TestNodePoolReconcileTracingRecordsError": {}, + "hypershift-operator/controllers/nodepool/tracing_test.go|TestNodePoolReconcileTracingSpanAttributes": {}, + "hypershift-operator/controllers/nodepool/tracing_test.go|TestNodePoolReconcileTracingSpanLinkFromTraceparent": {}, + "hypershift-operator/controllers/platform/aws/controller_test.go|TestDeleteAWSEndpointService": {}, + "hypershift-operator/controllers/platform/aws/controller_test.go|TestReconcileRequeuesWithJitter": {}, + "hypershift-operator/controllers/platform/azure/controller_test.go|TestDelete_WhenNoPEConnections_ItShouldProceedToDelete": {}, + "hypershift-operator/controllers/platform/azure/controller_test.go|TestDelete_WhenPEConnectionsExist_ItShouldRejectAndRequeue": {}, + "hypershift-operator/controllers/platform/azure/controller_test.go|TestDelete_WhenPLSAlreadyDeleted_ItShouldReturnCompleted": {}, + "hypershift-operator/controllers/platform/azure/controller_test.go|TestReconcile_WhenAdditionalAllowedSubscriptionsChange_ItShouldUpdatePLS": {}, + "hypershift-operator/controllers/platform/azure/controller_test.go|TestReconcile_WhenAdditionalAllowedSubscriptionsMatch_ItShouldNotUpdatePLS": {}, + "hypershift-operator/controllers/platform/azure/controller_test.go|TestReconcile_WhenCRDoesNotExist_ItShouldReturnEmptyResult": {}, + "hypershift-operator/controllers/platform/azure/controller_test.go|TestReconcile_WhenCRIsDeleted_AndDeleteSucceeds_ItShouldRemoveFinalizer": {}, + "hypershift-operator/controllers/platform/azure/controller_test.go|TestReconcile_WhenCRIsDeleted_ItShouldDeleteThePLS": {}, + "hypershift-operator/controllers/platform/azure/controller_test.go|TestReconcile_WhenHostedClusterIsPaused_ItShouldRequeueAfterPauseExpiry": {}, + "hypershift-operator/controllers/platform/azure/controller_test.go|TestReconcile_WhenLoadBalancerIPIsSet_ItShouldCreateAPLS": {}, + "hypershift-operator/controllers/platform/azure/controller_test.go|TestReconcile_WhenLoadBalancerIPNotSet_ItShouldRequeue": {}, + "hypershift-operator/controllers/platform/azure/controller_test.go|TestReconcile_WhenPLSAlreadyExists_ItShouldUpdateStatus": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestComputeServiceAdapterListServiceAttachmentsPagination": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestComputeServiceAdapterListSubnetworksPagination": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestDiscoverNATSubnetAPIError": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestDiscoverNATSubnetAllSubnetsInUse": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestDiscoverNATSubnetNoSubnets": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestDiscoverNATSubnetServiceAttachmentsError": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestDiscoverNATSubnetSingleAPICall": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestDiscoverNATSubnetSubnetInUseSkipsToNext": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestLookupForwardingRuleAPIError": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestLookupForwardingRuleMultipleResultsUsesFirst": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestLookupForwardingRuleNoResults": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestLookupForwardingRuleSingleResult": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestNATSubnetFilterFormat": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestReconcileNotFound": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestReconcilePausedUntil": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestReconcileSpecBothFieldsSetEarlyReturn": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestReconcileSpecForwardingRuleEmptyNetwork": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestReconcileSpecForwardingRuleLookupError": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestReconcileSpecForwardingRuleNotYetProvisioned": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestReconcileSpecPartialWriteForwardingRuleNamePreservedNATSubnetDiscovered": {}, + "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestReconcileSpecSetsForwardingRuleNameAndNATSubnet": {}, + "hypershift-operator/controllers/resourcebasedcpautoscaler/controller_test.go|TestReconcile": {}, + "hypershift-operator/controllers/resourcebasedcpautoscaler/controller_test.go|TestReconcileHostedClusterNotFound": {}, + "hypershift-operator/controllers/resourcebasedcpautoscaler/machine_sizes_cache_test.go|TestMachineSizesCache": {}, + "hypershift-operator/controllers/resourcebasedcpautoscaler/machine_sizes_cache_test.go|TestMachineSizesCacheMemoryFraction": {}, + "hypershift-operator/controllers/resourcebasedcpautoscaler/machine_sizes_cache_test.go|TestMachineSizesCacheValidation": {}, + "hypershift-operator/controllers/resourcebasedcpautoscaler/machine_sizes_cache_test.go|TestMachineSizesCacheWithSizingConfig": {}, + "hypershift-operator/controllers/scheduler/aws/dedicated_request_serving_nodes_test.go|TestHostedClusterScheduler": {}, + "hypershift-operator/controllers/scheduler/aws/dedicated_request_serving_nodes_test.go|TestHostedClusterSchedulerAndSizer": {}, + "hypershift-operator/controllers/scheduler/aws/dedicated_request_serving_nodes_test.go|TestNodeReaper": {}, + "hypershift-operator/controlplaneoperator-overrides/overrides_test.go|TestCPOImage": {}, + "hypershift-operator/controlplaneoperator-overrides/overrides_test.go|TestExistingOverride": {}, + "hypershift-operator/controlplaneoperator-overrides/overrides_test.go|TestLoadOverrides": {}, + "hypershift-operator/controlplaneoperator-overrides/overrides_test.go|TestLoadOverridesSample": {}, + "hypershift-operator/featuregate/feature_test.go|TestAllHypershiftOperatorFeatureGates": {}, + "hypershift-operator/featuregate/feature_test.go|TestGCPPlatformFeatureGate": {}, + "hypershift-operator/featuregate/feature_test.go|TestHCPEtcdBackupFeatureGate": {}, + "ignition-server/controllers/image_file_cache_test.go|TestDownloadImageErrorHashStripping": {}, + "ignition-server/controllers/local_ignitionprovider_test.go|TestCopyMCOOutputToMCC": {}, + "ignition-server/controllers/local_ignitionprovider_test.go|TestCopyMCOOutputToMCCMixedContent": {}, + "ignition-server/controllers/local_ignitionprovider_test.go|TestGetOrGenerateMCSCert": {}, + "ignition-server/controllers/local_ignitionprovider_test.go|TestGetOrGenerateMCSCertCacheReuse": {}, + "ignition-server/controllers/local_ignitionprovider_test.go|TestInvokeFeatureGateRenderScript": {}, + "ignition-server/controllers/local_ignitionprovider_test.go|TestInvokeFeatureGateRenderScriptEmbedsBinaryAndPaths": {}, + "ignition-server/controllers/local_ignitionprovider_test.go|TestSyncBuffer": {}, + "ignition-server/controllers/local_ignitionprovider_test.go|TestWriteInitialFiles": {}, + "ignition-server/controllers/local_ignitionprovider_test.go|TestWriteInitialFilesMultipleConfigEntries": {}, + "ignition-server/controllers/tokensecret_controller_test.go|TestCacheInvalidationOnCloudConfigHashChange": {}, + "ignition-server/controllers/tokensecret_controller_test.go|TestCloudConfigPendingReasonOnHashMismatch": {}, + "ignition-server/controllers/tokensecret_controller_test.go|TestGetTokenIDTimeLived": {}, + "ignition-server/controllers/tokensecret_controller_test.go|TestOldTokenFallbackWithCloudConfigHashMismatch": {}, + "ignition-server/controllers/tokensecret_controller_test.go|TestProcessedExpiredToken": {}, + "ignition-server/controllers/tokensecret_controller_test.go|TestReconcile": {}, + "ignition-server/controllers/tokensecret_controller_test.go|TestReconcileOSStreamPropagation": {}, + "ignition-server/controllers/tokensecret_controller_test.go|TestRotateTokenID": {}, + "ignition-server/controllers/tokensecret_controller_test.go|TestTokenIDNeedRotation": {}, + "karpenter-operator/controllers/karpenter/karpenter_controller_test.go|TestKarpenterDeletion": {}, + "karpenter-operator/controllers/karpenter/karpenter_controller_test.go|TestReconcileCRDsConcurrentAccess": {}, + "karpenter-operator/controllers/karpenter/karpenter_controller_test.go|TestReconcileDefaultOpenshiftEC2NodeClass": {}, + "karpenter-operator/controllers/karpenterignition/karpenterignition_controller_test.go|TestHostedClusterFromHCP": {}, + "karpenter-operator/controllers/karpenterignition/karpenterignition_controller_test.go|TestReconcile": {}, + "karpenter-operator/controllers/karpenterignition/karpenterignition_controller_test.go|TestReconcileKubeletConfigMap": {}, + "karpenter-operator/controllers/karpenterignition/karpenterignition_controller_test.go|TestReconcileKubeletConfigMapOrphanCleanup": {}, + "karpenter-operator/controllers/karpenterignition/karpenterignition_controller_test.go|TestReconcileVersionResolution": {}, + "karpenter-operator/controllers/karpenterignition/karpenterignition_controller_test.go|TestResolveVersion": {}, + "karpenter-operator/controllers/nodeclass/ec2_nodeclass_controller_test.go|TestReconcileCRDsStandaloneAdapterOnlyInstallsHyperShiftCRD": {}, + "karpenter-operator/controllers/nodeclass/ec2_nodeclass_controller_test.go|TestReconcileEC2NodeClass": {}, + "karpenter-operator/controllers/nodeclass/ec2_nodeclass_controller_test.go|TestReconcileEC2NodeClassUpgradePause": {}, + "karpenter-operator/controllers/nodeclass/ec2_nodeclass_controller_test.go|TestReconcileStatus": {}, + "karpenter-operator/controllers/nodeclass/ec2_nodeclass_controller_test.go|TestReconcileStatusIdempotency": {}, + "karpenter-operator/controllers/nodeclass/ec2_nodeclass_controller_test.go|TestReconcileStatusTagConflictCondition": {}, + "pkg/featuregates/featuregates_test.go|TestConfiguringUnknownFeatureSetErrors": {}, + "pkg/featuregates/featuregates_test.go|TestCreatingFeatureGates": {}, + "product-cli/cmd/nodepool/agent/create_test.go|TestNewCreateCommand": {}, + "product-cli/cmd/nodepool/agent/create_test.go|TestUpdateNodePool": {}, + "sharedingress-config-generator/config_test.go|TestGenerateConfig": {}, + "sharedingress-config-generator/controller_test.go|TestReconcile": {}, + "sharedingress-config-generator/controller_test.go|TestReconcileCoalescing": {}, + "support/api/scheme_test.go|TestEncode": {}, + "support/api/scheme_test.go|TestEncodeJSON": {}, + "support/api/scheme_test.go|TestEncodeJSON_Consistency": {}, + "support/api/scheme_test.go|TestEncodeJSON_EdgeCase_EpochTimestampInAnnotations": {}, + "support/api/scheme_test.go|TestEncodeJSON_EdgeCase_EpochTimestampInData": {}, + "support/api/scheme_test.go|TestEncodeJSON_NonMetaObject": {}, + "support/api/scheme_test.go|TestEncode_Consistency": {}, + "support/api/scheme_test.go|TestEncode_EdgeCase_EpochTimestampInAnnotations": {}, + "support/api/scheme_test.go|TestEncode_EdgeCase_EpochTimestampInLabels": {}, + "support/api/scheme_test.go|TestEncode_NonMetaObject": {}, + "support/awsutil/sg_test.go|TestUpdateResourceTags_FiltersAWSReservedKeys": {}, + "support/azureutil/azureutil_test.go|TestGetNetworkSecurityGroupNameFromNetworkSecurityGroupID": {}, + "support/capabilities/management_cluster_capabilities_test.go|TestDetectManagementCapabilities": {}, + "support/capabilities/management_cluster_capabilities_test.go|TestDetectNativeSidecarCapability": {}, + "support/catalogs/images_test.go|TestImageExistsFnGuestCluster": {}, + "support/certs/tls_test.go|TestCertChainingAttributesPresent": {}, + "support/certs/tls_test.go|TestReconcileSignedCertIdempotentWithDualStackIPs": {}, + "support/certs/tls_test.go|TestReconcileSignedCertWithCustomCAKeys": {}, + "support/certs/tls_test.go|TestValidateKeyPairConsidersAllFields": {}, + "support/certs/tls_test.go|TestValidateKeyPairConsidersExpiration": {}, + "support/certs/tls_test.go|TestValidateKeyPairItempotency": {}, + "support/certs/tls_test.go|TestValidateKeyPairRejectsMalformedIP": {}, + "support/config/resources_test.go|TestApplyResourceRequestOverrides": {}, + "support/config/resources_test.go|TestSetResources": {}, + "support/controlplane-component/controlplane-component_test.go|TestReconcile": {}, + "support/controlplane-component/defaults_test.go|TestComputeResourceHashConsistency": {}, + "support/controlplane-component/defaults_test.go|TestSetControlPlaneIsolationNodeFailureTolerations": {}, + "support/controlplane-component/generic-adapter_test.go|TestGenericAdapterReconcile": {}, + "support/controlplane-component/gvk_cache_test.go|TestGVKAccessCache": {}, + "support/controlplane-component/konnectivity-container_test.go|TestBuildContainer": {}, + "support/controlplane-component/konnectivity-container_test.go|TestBuildContainerDualMode": {}, + "support/controlplane-component/konnectivity-container_test.go|TestBuildContainerPreferIPv4": {}, + "support/gcputil/gcputil_test.go|TestBuildWorkloadIdentityCredentials": {}, + "support/gcputil/gcputil_test.go|TestBuildWorkloadIdentityCredentialsValidation": {}, + "support/globalconfig/imagecontentsource_test.go|TestReconcileMgmtImageRegistryOverrides": {}, + "support/k8sutil/serializer_test.go|TestDeserializeResource": {}, + "support/k8sutil/serializer_test.go|TestSerializeDeserializeRoundTrip": {}, + "support/k8sutil/serializer_test.go|TestSerializeResource": {}, + "support/konnectivityproxy/dialer_test.go|TestKonnectivityHealth": {}, + "support/konnectivityproxy/dialer_test.go|TestKonnectivityHealthEndRetry": {}, + "support/konnectivityproxy/dialer_test.go|TestKonnectivityHealthEndRetryPreventsStubbornFlag": {}, + "support/konnectivityproxy/dialer_test.go|TestKonnectivityHealthRecovery": {}, + "support/konnectivityproxy/resolver_test.go|TestFallbackIPsContext": {}, + "support/konnectivityproxy/resolver_test.go|TestProxyResolverResolve": {}, + "support/konnectivityproxy/resolver_test.go|TestProxyResolverResolveGuestDNSSuccess": {}, + "support/konnectivityproxy/resolver_test.go|TestResolveAllIPs": {}, + "support/konnectivityproxy/resolver_test.go|TestResolveAllIPsOrdering": {}, + "support/labelenforcingclient/labelenforcer_test.go|TestLabelEnforcingClientEnforcesLabel": {}, + "support/labelenforcingclient/labelenforcer_test.go|TestLabelEnforcingUpsertProvider": {}, + "support/metrics/sets_test.go|TestLoadMetricsConfig": {}, + "support/netutil/networking_test.go|TestIsMultusDisabled": {}, + "support/netutil/route_test.go|TestShortenName": {}, + "support/netutil/route_test.go|TestShortenNameIsDifferent": {}, + "support/netutil/route_test.go|TestShortenNameReturnShortNames": {}, + "support/podspec/containers_test.go|TestEnforceRestrictedSecurityContextToContainers": {}, + "support/podspec/containers_test.go|TestEnforceRestrictedSecurityContextToContainers_InvalidCapabilities": {}, + "support/releaseinfo/cincinnati_version_resolver_test.go|TestCincinnatiVersionResolver_WhenCacheIsExpired_ItShouldReQueryAPI": {}, + "support/releaseinfo/cincinnati_version_resolver_test.go|TestCincinnatiVersionResolver_WhenCacheIsFresh_ItShouldReturnCachedWithoutHTTPCall": {}, + "support/releaseinfo/cincinnati_version_resolver_test.go|TestCincinnatiVersionResolver_WhenCustomChannel_ItShouldPassChannelToAPI": {}, + "support/releaseinfo/cincinnati_version_resolver_test.go|TestCincinnatiVersionResolver_WhenNon200Status_ItShouldReturnError": {}, + "support/releaseinfo/cincinnati_version_resolver_test.go|TestCincinnatiVersionResolver_WhenValidResponse_ItShouldReturnReleaseImage": {}, + "support/releaseinfo/cincinnati_version_resolver_test.go|TestCincinnatiVersionResolver_WhenVersionNotInGraph_ItShouldReturnError": {}, + "support/releaseinfo/deserialize_test.go|TestDeserializeImageMetadata": {}, + "support/releaseinfo/deserialize_test.go|TestDeserializeImageMetadataMultiStreamContent": {}, + "support/releaseinfo/deserialize_test.go|TestDeserializeImageMetadataStreamsOnly": {}, + "support/releaseinfo/registry_image_content_policies_test.go|TestProviderWithOpenShiftImageRegistryOverridesDecorator_Lookup": {}, + "support/releaseinfo/registry_image_content_policies_test.go|TestProviderWithOpenShiftImageRegistryOverridesDecorator_LookupWithNilRepoSetupFn": {}, + "support/releaseinfo/releaseinfo_test.go|TestReleaseInfoKubeVirt": {}, + "support/releaseinfo/releaseinfo_test.go|TestReleaseInfoPowerVS": {}, + "support/statuspatching/statuspatching_test.go|TestPatchStatus": {}, + "support/statuspatching/statuspatching_test.go|TestPatchStatusCondition": {}, + "support/statuspatching/statuspatching_test.go|TestPatchStatusCondition_GetFailure": {}, + "support/statuspatching/statuspatching_test.go|TestPatchStatusCondition_RetryOnConflict": {}, + "support/statuspatching/statuspatching_test.go|TestPatchStatus_GetFailure": {}, + "support/statuspatching/statuspatching_test.go|TestPatchStatus_MutateError": {}, + "support/statuspatching/statuspatching_test.go|TestPatchStatus_RetryOnConflict": {}, + "support/supportedversion/version_test.go|TestFindLatestSupportedVersionWithSorting": {}, + "support/supportedversion/version_test.go|TestLookupDefaultOCPVersion_ListFailure": {}, + "support/supportedversion/version_test.go|TestRetrieveSupportedOCPVersion": {}, + "support/supportedversion/version_test.go|TestRetrieveSupportedOCPVersionWithRCFiltering": {}, + "support/supportedversion/version_test.go|TestRetrieveSupportedOCPVersion_ListFailure": {}, + "support/supportedversion/version_test.go|TestSupportedVersions": {}, + "support/tracing/tracing_test.go|TestInitProvider_NoopWhenUnset": {}, + "support/tracing/tracing_test.go|TestInitProvider_SDKProviderWhenSet": {}, + "support/tracing/tracing_test.go|TestInitProvider_ShutdownIsIdempotent": {}, + "support/tracing/tracing_test.go|TestParseSampler_AlwaysOff": {}, + "support/tracing/tracing_test.go|TestParseSampler_DefaultIsParentBasedAlwaysOn": {}, + "support/tracing/tracing_test.go|TestParseSampler_InvalidArgDefaultsToOne": {}, + "support/tracing/tracing_test.go|TestParseSampler_ParentBasedTraceIDRatio": {}, + "support/tracing/tracing_test.go|TestParseSampler_TraceIDRatio": {}, + "support/tracing/tracing_test.go|TestSpanLinkFromAnnotations_InvalidTraceparent": {}, + "support/tracing/tracing_test.go|TestSpanLinkFromAnnotations_NilAnnotations": {}, + "support/tracing/tracing_test.go|TestSpanLinkFromAnnotations_NoAnnotation": {}, + "support/tracing/tracing_test.go|TestSpanLinkFromAnnotations_ValidTraceparent": {}, + "support/tracing/tracing_test.go|TestTracer_CreatesSpansWithCorrectName": {}, + "support/tracing/tracing_test.go|TestTracer_ReturnsNamedTracer": {}, + "support/tracing/tracing_test.go|TestTracer_SpanRecordsErrorStatus": {}, + "support/upsert/apply_test.go|TestApplyManifest": {}, + "support/upsert/apply_test.go|TestApplyManifestLabelRemoval": {}, + "support/upsert/apply_test.go|TestApplyManifestLabelRemovalOnCreate": {}, + "support/upsert/apply_test.go|TestApplyManifestLabelRemovalWithEmptyLabels": {}, + "support/upsert/apply_test.go|TestApplyManifest_DesiredStateHash": {}, + "support/upsert/apply_test.go|TestApplyManifest_DesiredStateHashEdgeCases": {}, + "support/util/cleanup_tracker_test.go|TestConcurrentAccess": {}, + "support/util/cleanup_tracker_test.go|TestMultipleHCPs": {}, + "support/util/cleanup_tracker_test.go|TestNewCleanupTracker": {}, + "support/util/cleanup_tracker_test.go|TestRecordFailure": {}, + "support/util/cleanup_tracker_test.go|TestResetFailures": {}, + "support/util/cleanup_tracker_test.go|TestShouldSkipCleanup_BelowMaxDuration": {}, + "support/util/cleanup_tracker_test.go|TestShouldSkipCleanup_BelowMaxFailures": {}, + "support/util/cleanup_tracker_test.go|TestShouldSkipCleanup_KubeAPIServerAvailable": {}, + "support/util/cleanup_tracker_test.go|TestShouldSkipCleanup_KubeAPIServerUnavailable": {}, + "support/util/cleanup_tracker_test.go|TestShouldSkipCleanup_MaxDurationExceeded": {}, + "support/util/cleanup_tracker_test.go|TestShouldSkipCleanup_MaxFailuresExceeded": {}, + "support/util/imagemetadata_test.go|TestCacheCleanupOnExpiration": {}, + "support/util/imagemetadata_test.go|TestMirrorAvailabilityCache": {}, + "support/util/imagemetadata_test.go|TestSeekOverride": {}, + "support/util/imagemetadata_test.go|TestSeekOverrideFallsBackWhenMirrorUnavailable": {}, + "support/util/imagemetadata_test.go|TestSeekOverrideWithCache": {}, + "support/util/pausereconcile_test.go|TestGenerateReconciliationPausedCondition": {}, + "support/util/registryoverride/registryoverride_test.go|TestReplace": {}, + "support/util/registryoverride/registryoverride_test.go|TestReplace_DoesNotMutateOverrides": {}, + "support/util/util_test.go|TestCompressDecompress": {}, +} From b0aca0c3463fe3e5921288742deb5d771d2b2a40 Mon Sep 17 00:00:00 2001 From: Bryan Cox Date: Wed, 30 Sep 2026 18:27:24 -0400 Subject: [PATCH 4/8] fix(lint): preserve production provenance across test packages Share analysis facts between production and external test packages to exclude generated/test-only declarations and detect split tests across package variants. Use repository set conventions, remove stale baseline entries, and validate that each remaining exception names an existing test. Restore unrelated test consolidations so this branch remains within the analyzer's scope. Signed-off-by: Bryan Cox Assisted-by: LLM --- TESTING.md | 2 + cmd/cluster/core/destroy_test.go | 70 ++++---- cmd/infra/aws/util/sts_test.go | 67 +++++--- hack/tools/hypershiftlinter/README.md | 10 ++ .../analyzers/testfuncstructure/legacy.go | 37 +++-- .../testfuncstructure/legacy_test.go | 48 ++++++ .../analyzers/testfuncstructure/metadata.go | 95 +++++++++++ .../testdata/src/a/ambiguous/production.go | 2 +- .../src/a/crosspackage/external_test.go | 21 +++ .../src/a/crosspackage/internal_test.go | 12 ++ .../testdata/src/a/crosspackage/production.go | 11 ++ .../testdata/src/a/external/external.go | 2 +- .../testdata/src/a/good/generated.go | 1 + .../testdata/src/a/legacy/production.go | 2 +- .../src/a/legacyambiguous/production.go | 2 +- .../testdata/src/a/precedence/production.go | 2 +- .../testdata/src/a/provenance/generated.go | 9 + .../testdata/src/a/provenance/helper_test.go | 9 + .../testdata/src/a/provenance/production.go | 5 + .../src/a/provenance/production_test.go | 25 +++ .../testdata/src/a/scenarioonly/production.go | 2 +- .../testdata/src/a/split/production.go | 2 +- .../src/a/unrelated/common/production.go | 3 + .../src/a/unrelated/common/production_test.go | 11 ++ .../a/unrelated/inner/common/production.go | 3 + .../src/support/util/cleanup_tracker_test.go | 7 + .../testdata/src/support/util/production.go | 3 + .../testdata/src/test/e2e/good/production.go | 2 +- .../src/test/envtest/good/production.go | 2 +- .../src/test/integration/good/production.go | 2 +- .../test/reqserving-e2e/good/production.go | 2 +- .../testfuncstructure/testfuncstructure.go | 155 +++++++++--------- .../testfuncstructure_test.go | 63 +++++-- support/util/loglevel_test.go | 43 +++-- 34 files changed, 541 insertions(+), 191 deletions(-) create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/metadata.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/crosspackage/external_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/crosspackage/internal_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/crosspackage/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/provenance/generated.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/provenance/helper_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/provenance/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/provenance/production_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unrelated/common/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unrelated/common/production_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unrelated/inner/common/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/support/util/cleanup_tracker_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/support/util/production.go diff --git a/TESTING.md b/TESTING.md index 6e33d094fd11..28db3f6b7d01 100644 --- a/TESTING.md +++ b/TESTING.md @@ -47,6 +47,8 @@ Do not use generic or disconnected test function names like `TestNodePoolFeature Each production function or method must map to at most one top-level unit test function. If a function is complex enough to warrant many test cases, use table-driven tests or `t.Run` subtests within a single test function, with the "When...it should..." naming for each case. +This limit includes tests in both the production package and its external `_test` package. + Prefer `Test_` for method tests so methods with common names such as `Reconcile`, `Validate`, and `Run` map unambiguously: ```go diff --git a/cmd/cluster/core/destroy_test.go b/cmd/cluster/core/destroy_test.go index ab8684abbd49..d6798c4aa785 100644 --- a/cmd/cluster/core/destroy_test.go +++ b/cmd/cluster/core/destroy_test.go @@ -367,40 +367,6 @@ func TestForceRemoveAllFinalizers(t *testing.T) { g.Expect(err).ToNot(HaveOccurred()) g.Expect(updatedHC.Finalizers).To(Equal([]string{destroyFinalizer})) }) - - t.Run("When client operations fail, it should return an aggregated error", func(t *testing.T) { - g := NewGomegaWithT(t) - ctx := context.Background() - - hc := &hyperv1.HostedCluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-cluster", - Namespace: "clusters", - Finalizers: []string{destroyFinalizer, "hypershift.openshift.io/finalizer"}, - }, - } - - c := fake.NewClientBuilder(). - WithScheme(hyperapi.Scheme). - WithObjects(hc). - WithInterceptorFuncs(interceptor.Funcs{ - Patch: func(_ context.Context, _ client.WithWatch, _ client.Object, _ client.Patch, _ ...client.PatchOption) error { - return fmt.Errorf("API server unavailable") - }, - }). - Build() - - opts := &DestroyOptions{ - Name: "test-cluster", - Namespace: "clusters", - Log: log.Log, - } - - err := forceRemoveAllFinalizers(ctx, hc, opts, c) - g.Expect(err).To(HaveOccurred()) - g.Expect(err.Error()).To(ContainSubstring("force removal encountered")) - g.Expect(err.Error()).To(ContainSubstring("API server unavailable")) - }) } func TestStripFinalizers(t *testing.T) { @@ -537,6 +503,42 @@ func TestStripNodePoolFinalizers(t *testing.T) { }) } +func TestForceRemoveAllFinalizersErrors(t *testing.T) { + t.Run("When client operations fail, it should return an aggregated error", func(t *testing.T) { + g := NewGomegaWithT(t) + ctx := context.Background() + + hc := &hyperv1.HostedCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: "clusters", + Finalizers: []string{destroyFinalizer, "hypershift.openshift.io/finalizer"}, + }, + } + + c := fake.NewClientBuilder(). + WithScheme(hyperapi.Scheme). + WithObjects(hc). + WithInterceptorFuncs(interceptor.Funcs{ + Patch: func(_ context.Context, _ client.WithWatch, _ client.Object, _ client.Patch, _ ...client.PatchOption) error { + return fmt.Errorf("API server unavailable") + }, + }). + Build() + + opts := &DestroyOptions{ + Name: "test-cluster", + Namespace: "clusters", + Log: log.Log, + } + + err := forceRemoveAllFinalizers(ctx, hc, opts, c) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("force removal encountered")) + g.Expect(err.Error()).To(ContainSubstring("API server unavailable")) + }) +} + func TestGetCluster(t *testing.T) { t.Run("When an injected client contains a hosted cluster, it should return that cluster", func(t *testing.T) { g := NewGomegaWithT(t) diff --git a/cmd/infra/aws/util/sts_test.go b/cmd/infra/aws/util/sts_test.go index e48878cbe689..ed58709af79a 100644 --- a/cmd/infra/aws/util/sts_test.go +++ b/cmd/infra/aws/util/sts_test.go @@ -242,28 +242,6 @@ func TestParseSTSCredentialsFile(t *testing.T) { } }, }, - { - name: "When given real-world STS credentials, it should preserve all credential fields", - fileContent: `{ - "Credentials": { - "AccessKeyId": "ASIAIOSFODNN7EXAMPLE", - "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY", - "SessionToken": "FwoGZXIvYXdzEBYaDHExampleToken1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", - "Expiration": "2025-01-30T12:00:00Z" - } -}`, - validateCreds: func(t *testing.T, creds *aws.Credentials) { - if creds.AccessKeyID != "ASIAIOSFODNN7EXAMPLE" { - t.Errorf("Expected AccessKeyID ASIAIOSFODNN7EXAMPLE, got %s", creds.AccessKeyID) - } - if creds.SecretAccessKey == "" { - t.Error("Expected non-empty SecretAccessKey") - } - if creds.SessionToken == "" { - t.Error("Expected non-empty SessionToken") - } - }, - }, } for _, tc := range testCases { @@ -317,3 +295,48 @@ func TestParseSTSCredentialsFile(t *testing.T) { }) } } + +func TestParseSTSCredentialsFile_RealWorldFormat(t *testing.T) { + realWorldJSON := `{ + "Credentials": { + "AccessKeyId": "ASIAIOSFODNN7EXAMPLE", + "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY", + "SessionToken": "FwoGZXIvYXdzEBYaDHExampleToken1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", + "Expiration": "2025-01-30T12:00:00Z" + } +}` + + tmpFile, err := os.CreateTemp(t.TempDir(), "real-world-sts-*.json") + if err != nil { + t.Fatalf("Failed to create temp file: %v", err) + } + defer tmpFile.Close() + + if _, err := tmpFile.WriteString(realWorldJSON); err != nil { + t.Fatalf("Failed to write to temp file: %v", err) + } + + creds, err := ParseSTSCredentialsFile(tmpFile.Name()) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + return + } + + if creds == nil { + t.Fatal("Expected non-nil credentials") + return + } + + expectedAccessKey := "ASIAIOSFODNN7EXAMPLE" + if creds.AccessKeyID != expectedAccessKey { + t.Errorf("Expected AccessKeyID %s, got %s", expectedAccessKey, creds.AccessKeyID) + } + + if creds.SecretAccessKey == "" { + t.Error("Expected non-empty SecretAccessKey") + } + + if creds.SessionToken == "" { + t.Error("Expected non-empty SessionToken") + } +} diff --git a/hack/tools/hypershiftlinter/README.md b/hack/tools/hypershiftlinter/README.md index d55447c02a25..0c66c5104d42 100644 --- a/hack/tools/hypershiftlinter/README.md +++ b/hack/tools/hypershiftlinter/README.md @@ -87,6 +87,12 @@ take precedence over scenario suffixes, so `TestReconcileErrors` maps to a real `ReconcileErrors` function before it can be treated as another `Reconcile` scenario. +External tests are matched to the actual package-under-test import path, not +another dependency with the same package name. Analysis facts carry production +declaration provenance and internal-test mappings into the external test +variant. This excludes exported test helpers and generated methods from the +production index and enforces the one-test limit across both package variants. + The analyzer is enabled by both the root and `api/` module lint configurations. When several top-level tests map to one production symbol, consolidate their @@ -117,6 +123,10 @@ test. Remove an entry when its test is renamed or consolidated. An accepted limitation is that an existing declaration remains exempt while its baseline entry exists, even if its body changes. +The linter test suite checks that every baseline entry names an existing test +declaration. Delete obsolete entries when tests are moved, renamed, or removed; +otherwise `make test-linter` fails. + ## Legacy E2E utility migration exceptions `e2eutilallowlist` prevents new v2 dependencies on `test/e2e/util` and its diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go index 7dd15673670a..3c54fe65072a 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go @@ -1,8 +1,10 @@ package testfuncstructure +import "k8s.io/apimachinery/pkg/util/sets" + // legacyExceptions contains exact pre-existing test declarations that do not // satisfy TESTING.md. Remove an entry when the corresponding test is migrated. -var legacyExceptions = map[string]struct{}{ +var legacyExceptions = sets.Set[string]{ "api/util/ipnet/ipnet_test.go|TestUnmarshal": {}, "cmd/cluster/agent/create_test.go|TestCreateCluster": {}, "cmd/cluster/aws/create_test.go|TestCreateCluster": {}, @@ -20,6 +22,8 @@ var legacyExceptions = map[string]struct{}{ "cmd/cluster/core/create_test.go|TestGetServicePublishingStrategyMapping": {}, "cmd/cluster/core/create_test.go|TestOVNKubernetesMTUFlag": {}, "cmd/cluster/core/create_test.go|TestPrototypeResources": {}, + "cmd/cluster/core/destroy_test.go|TestForceRemoveAllFinalizers": {}, + "cmd/cluster/core/destroy_test.go|TestForceRemoveAllFinalizersErrors": {}, "cmd/cluster/gcp/create_test.go|TestCreateCluster": {}, "cmd/cluster/gcp/create_test.go|TestValidateGCPOptions": {}, "cmd/cluster/kubevirt/create_test.go|TestCreateCluster": {}, @@ -34,8 +38,12 @@ var legacyExceptions = map[string]struct{}{ "cmd/infra/aws/create_operator_roles_test.go|TestCreateOperatorRolesValidate": {}, "cmd/infra/aws/destroy_test.go|TestEmptyBucket": {}, "cmd/infra/aws/destroy_test.go|TestEmptyBucket_Pagination": {}, + "cmd/infra/aws/route53_test.go|TestCreatePrivateZone": {}, + "cmd/infra/aws/route53_test.go|TestCreatePrivateZoneUsesStableCallerReferenceAcrossRetries": {}, "cmd/infra/aws/util/sts_test.go|TestNewSTSSession_Validation": {}, "cmd/infra/aws/util/sts_test.go|TestNewSTSSession_ValidationOrder": {}, + "cmd/infra/aws/util/sts_test.go|TestParseSTSCredentialsFile": {}, + "cmd/infra/aws/util/sts_test.go|TestParseSTSCredentialsFile_RealWorldFormat": {}, "cmd/infra/aws/util/util_test.go|TestGetSession": {}, "cmd/infra/aws/util/util_test.go|TestGetSession_DefaultChainRoleAssumption": {}, "cmd/infra/aws/util/util_test.go|TestNewSession": {}, @@ -58,6 +66,7 @@ var legacyExceptions = map[string]struct{}{ "cmd/install/install_test.go|TestCRDIncludeFilter": {}, "cmd/install/install_test.go|TestHyperShiftOperatorManifests_SharedIngress": {}, "cmd/install/install_test.go|TestHyperShiftOperatorManifests_WebhookFlags": {}, + "cmd/install/install_test.go|TestHyperShiftOperatorPodDisruptionBudget": {}, "cmd/install/install_test.go|TestNewInstallOptionsWithDefaults": {}, "cmd/install/install_test.go|TestNewInstallOptionsWithDefaults_InstallScope": {}, "cmd/install/install_test.go|TestOutputsHelpers": {}, @@ -269,6 +278,8 @@ var legacyExceptions = map[string]struct{}{ "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestAWSSecurityGroupTags": {}, "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestControlPlaneComponents": {}, "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestEventHandling": {}, + "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestHealthCheckKASEndpoint": {}, + "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestHealthCheckKASEndpointMetrics": {}, "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestNonReadyInfraTriggersRequeueAfter": {}, "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestReconcileDefaultSecurityGroup": {}, "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestReconcileDefaultSecurityGroup_GenerationConflict": {}, @@ -286,6 +297,7 @@ var legacyExceptions = map[string]struct{}{ "control-plane-operator/controllers/hostedcontrolplane/ingress/router_test.go|TestReconcileRouterService_WhenExternalAWS_ItShouldSetInternetFacingScheme": {}, "control-plane-operator/controllers/hostedcontrolplane/ingress/router_test.go|TestReconcileRouterService_WhenSwitchingMode_ItShouldRemoveConflictingAnnotation": {}, "control-plane-operator/controllers/hostedcontrolplane/kas/healthcheck_test.go|TestGetHealthcheckEndpoint": {}, + "control-plane-operator/controllers/hostedcontrolplane/kas/metrics_test.go|TestKASHealthMetricsOperations": {}, "control-plane-operator/controllers/hostedcontrolplane/kas/service_test.go|TestKonnectivityServiceReconcile": {}, "control-plane-operator/controllers/hostedcontrolplane/kas/service_test.go|TestReconcilePrivateService": {}, "control-plane-operator/controllers/hostedcontrolplane/kas/service_test.go|TestReconcilePrivateServiceAWSNLBAnnotation": {}, @@ -334,7 +346,9 @@ var legacyExceptions = map[string]struct{}{ "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/powervs/deployment_test.go|TestAdaptDeploymentWithAssets": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_credential_operator/component_test.go|TestCloudCredentialOperatorOptions": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_credential_operator/deployment_test.go|TestAdaptDeployment": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_credential_operator/deployment_test.go|TestAdaptDeploymentTLS": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/cloud_credential_operator/deployment_test.go|TestAdaptDeploymentUpdatesContainer": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/configoperator/deployment_test.go|TestAdaptDeploymentIBMCloudControllerSelection": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/controlplaneoperator/deployment_test.go|TestAdaptDeploymentImageOverrides": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/controlplaneoperator/deployment_test.go|TestAdaptDeploymentWatchListClientEnv": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/csi/kubevirt/config_test.go|TestAdaptConfigMapConsistentOrdering": {}, @@ -408,6 +422,8 @@ var legacyExceptions = map[string]struct{}{ "control-plane-operator/controllers/hostedcontrolplane/v2/nto/servicemonitor_test.go|TestAdaptServiceMonitor": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/nto/servicemonitor_test.go|TestAdaptServiceMonitorErrorCases": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/nto/servicemonitor_test.go|TestAdaptServiceMonitorMetricsSetConfigurations": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/oapi/deployment_test.go|TestAdaptCombinedPullSecret": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/oapi/deployment_test.go|TestAdaptCombinedPullSecretMissingPullSecret": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/oapi/deployment_test.go|TestAdaptDeploymentOAPILogLevel": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/oapi/deployment_test.go|TestReconcileOpenshiftAPIServerDeploymentTrustBundle": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/oauth/deployment_test.go|TestAdaptDeployment": {}, @@ -461,6 +477,7 @@ var legacyExceptions = map[string]struct{}{ "control-plane-operator/hostedclusterconfigoperator/controllers/resources/monitoring/metricsforwarder_test.go|TestReconcileMetricsForwarderDeployment": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/monitoring/metricsforwarder_test.go|TestReconcileMetricsForwarderDeployment_ConfigHashChangeTrigersRollout": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/network/reconcile_test.go|TestReconcileDefaultIngressController": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/olm/catalogs_test.go|TestReconcileCatalogSourcePreservesExistingAnnotations": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestConnectionErrorTracking": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestDestroyCloudResources": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestDestroyCloudResourcesWithKASUnavailable": {}, @@ -561,6 +578,8 @@ var legacyExceptions = map[string]struct{}{ "hypershift-operator/controllers/hostedcluster/network_policies_test.go|TestReconcileNetworkPolicies_HCPEgressBlockCIDRs": {}, "hypershift-operator/controllers/hostedcluster/network_policies_test.go|TestReconcileNetworkPolicies_LoadBalancerOauth": {}, "hypershift-operator/controllers/hostedcluster/network_policies_test.go|TestReconcileNetworkPolicies_OpenshiftIngressPolicy": {}, + "hypershift-operator/controllers/hostedcluster/network_policies_test.go|TestReconcilePrivateRouterNetworkPolicy_DualStack": {}, + "hypershift-operator/controllers/hostedcluster/network_policies_test.go|TestReconcileVirtLauncherNetworkPolicyEgressPeers": {}, "hypershift-operator/controllers/hostedcluster/reconcile_report_test.go|TestBlockingBehavior": {}, "hypershift-operator/controllers/hostedcluster/reconcile_report_test.go|TestExecuteOrBlock": {}, "hypershift-operator/controllers/hostedcluster/reconcile_report_test.go|TestReconcileReport": {}, @@ -601,6 +620,7 @@ var legacyExceptions = map[string]struct{}{ "hypershift-operator/controllers/nodepool/capi_test.go|TestPauseUnpauseCycle": {}, "hypershift-operator/controllers/nodepool/capi_test.go|TestReconcileMachineHealthCheck": {}, "hypershift-operator/controllers/nodepool/capi_test.go|TestReconcileMachineHealthCheckAnnotation": {}, + "hypershift-operator/controllers/nodepool/capi_test.go|TestReconcileSelectorDropsStaleLabels": {}, "hypershift-operator/controllers/nodepool/conditions_test.go|TestGenerateReconciliationPausedCondition": {}, "hypershift-operator/controllers/nodepool/config_test.go|TestCloudConfigHash": {}, "hypershift-operator/controllers/nodepool/config_test.go|TestMissingCoreConfigError": {}, @@ -721,6 +741,7 @@ var legacyExceptions = map[string]struct{}{ "karpenter-operator/controllers/nodeclass/ec2_nodeclass_controller_test.go|TestReconcileStatusTagConflictCondition": {}, "pkg/featuregates/featuregates_test.go|TestConfiguringUnknownFeatureSetErrors": {}, "pkg/featuregates/featuregates_test.go|TestCreatingFeatureGates": {}, + "product-cli/cmd/kubeconfig/create_test.go|TestNewCreateCommandRunE": {}, "product-cli/cmd/nodepool/agent/create_test.go|TestNewCreateCommand": {}, "product-cli/cmd/nodepool/agent/create_test.go|TestUpdateNodePool": {}, "sharedingress-config-generator/config_test.go|TestGenerateConfig": {}, @@ -829,23 +850,13 @@ var legacyExceptions = map[string]struct{}{ "support/upsert/apply_test.go|TestApplyManifestLabelRemovalWithEmptyLabels": {}, "support/upsert/apply_test.go|TestApplyManifest_DesiredStateHash": {}, "support/upsert/apply_test.go|TestApplyManifest_DesiredStateHashEdgeCases": {}, - "support/util/cleanup_tracker_test.go|TestConcurrentAccess": {}, - "support/util/cleanup_tracker_test.go|TestMultipleHCPs": {}, - "support/util/cleanup_tracker_test.go|TestNewCleanupTracker": {}, - "support/util/cleanup_tracker_test.go|TestRecordFailure": {}, - "support/util/cleanup_tracker_test.go|TestResetFailures": {}, - "support/util/cleanup_tracker_test.go|TestShouldSkipCleanup_BelowMaxDuration": {}, - "support/util/cleanup_tracker_test.go|TestShouldSkipCleanup_BelowMaxFailures": {}, - "support/util/cleanup_tracker_test.go|TestShouldSkipCleanup_KubeAPIServerAvailable": {}, - "support/util/cleanup_tracker_test.go|TestShouldSkipCleanup_KubeAPIServerUnavailable": {}, - "support/util/cleanup_tracker_test.go|TestShouldSkipCleanup_MaxDurationExceeded": {}, - "support/util/cleanup_tracker_test.go|TestShouldSkipCleanup_MaxFailuresExceeded": {}, "support/util/imagemetadata_test.go|TestCacheCleanupOnExpiration": {}, "support/util/imagemetadata_test.go|TestMirrorAvailabilityCache": {}, "support/util/imagemetadata_test.go|TestSeekOverride": {}, "support/util/imagemetadata_test.go|TestSeekOverrideFallsBackWhenMirrorUnavailable": {}, "support/util/imagemetadata_test.go|TestSeekOverrideWithCache": {}, - "support/util/pausereconcile_test.go|TestGenerateReconciliationPausedCondition": {}, + "support/util/loglevel_test.go|TestLogLevelToEtcdLevel": {}, + "support/util/loglevel_test.go|TestLogLevelToEtcdLevelPanicsOnUnsupported": {}, "support/util/registryoverride/registryoverride_test.go|TestReplace": {}, "support/util/registryoverride/registryoverride_test.go|TestReplace_DoesNotMutateOverrides": {}, "support/util/util_test.go|TestCompressDecompress": {}, diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy_test.go new file mode 100644 index 000000000000..bd51a7f0672e --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy_test.go @@ -0,0 +1,48 @@ +package testfuncstructure + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "path/filepath" + "runtime" + "strings" + "testing" + + . "github.com/onsi/gomega" + + "k8s.io/apimachinery/pkg/util/sets" +) + +func TestLegacyExceptions(t *testing.T) { + g := NewWithT(t) + _, source, _, ok := runtime.Caller(0) + g.Expect(ok).To(BeTrue()) + repoRoot := filepath.Clean(filepath.Join(filepath.Dir(source), "../../../../..")) + declarations := map[string][]string{} + + for _, exception := range sets.List(legacyExceptions) { + t.Run(fmt.Sprintf("When %s is baselined, it should name an existing test", exception), func(t *testing.T) { + g := NewWithT(t) + filename, name, ok := strings.Cut(exception, "|") + g.Expect(ok).To(BeTrue()) + g.Expect(filename).To(HaveSuffix("_test.go")) + g.Expect(name).To(HavePrefix("Test")) + + names, loaded := declarations[filename] + if !loaded { + file, err := parser.ParseFile(token.NewFileSet(), filepath.Join(repoRoot, filename), nil, parser.SkipObjectResolution) + g.Expect(err).ToNot(HaveOccurred()) + for _, declaration := range file.Decls { + if function, ok := declaration.(*ast.FuncDecl); ok && function.Recv == nil { + names = append(names, function.Name.Name) + } + } + declarations[filename] = names + } + + g.Expect(names).To(ContainElement(name)) + }) + } +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/metadata.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/metadata.go new file mode 100644 index 000000000000..075e66dbd6d2 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/metadata.go @@ -0,0 +1,95 @@ +package testfuncstructure + +import ( + "go/token" + "go/types" + "path/filepath" + + "golang.org/x/tools/go/analysis" +) + +// productionMetadata preserves declaration provenance when an external test +// package imports the package under test, including its internal test variant. +type productionMetadata struct { + SymbolIDs []string + Tests []testMetadata +} + +type testMetadata struct { + SymbolID string + Name string + Filename string + Kind resolutionKind + Priority int + Suppressed bool +} + +// AFact marks productionMetadata as a serializable analysis fact. +func (*productionMetadata) AFact() {} + +// String describes the fact in analysistest fixture expectations. +func (*productionMetadata) String() string { + return "testfuncstructure production symbols" +} + +func exportProductionMetadata(pass *analysis.Pass, symbols []*productionSymbol, groups map[string][]*testFunction) { + metadata := &productionMetadata{SymbolIDs: make([]string, 0, len(symbols))} + for _, symbol := range symbols { + metadata.SymbolIDs = append(metadata.SymbolIDs, symbol.id) + for _, test := range groups[symbol.id] { + metadata.Tests = append(metadata.Tests, testMetadata{ + SymbolID: symbol.id, + Name: test.name, + Filename: filepath.Base(test.filename), + Kind: test.resolution.kind, + Priority: test.resolution.priority, + Suppressed: test.suppressed, + }) + } + } + pass.ExportPackageFact(metadata) +} + +func collectImportedTests(pass *analysis.Pass, symbolsByID map[string]*productionSymbol) []*testFunction { + target := externalPackageUnderTest(pass) + if target == nil || len(pass.Files) == 0 { + return nil + } + var metadata productionMetadata + if !pass.ImportPackageFact(target, &metadata) { + return nil + } + + directory := filepath.Dir(pass.Fset.Position(pass.Files[0].Pos()).Filename) + tests := make([]*testFunction, 0, len(metadata.Tests)) + for _, recorded := range metadata.Tests { + symbol := symbolsByID[recorded.SymbolID] + if symbol == nil { + continue + } + + filename := filepath.Join(directory, recorded.Filename) + namePos, nameEnd := token.NoPos, token.NoPos + if function, ok := target.Scope().Lookup(recorded.Name).(*types.Func); ok { + if file := pass.Fset.File(function.Pos()); file != nil { + filename = file.Name() + namePos = function.Pos() + nameEnd = min(namePos+token.Pos(len(recorded.Name)), file.Pos(file.Size())) + } + } + tests = append(tests, &testFunction{ + filename: filename, + name: recorded.Name, + namePos: namePos, + nameEnd: nameEnd, + imported: true, + suppressed: recorded.Suppressed, + resolution: resolution{ + kind: recorded.Kind, + target: symbol, + priority: recorded.Priority, + }, + }) + } + return tests +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/ambiguous/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/ambiguous/production.go index 059ecb51b593..24f67a622e34 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/ambiguous/production.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/ambiguous/production.go @@ -1,4 +1,4 @@ -package ambiguous +package ambiguous // want package:"testfuncstructure production symbols" type First struct{} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/crosspackage/external_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/crosspackage/external_test.go new file mode 100644 index 000000000000..052cd7365161 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/crosspackage/external_test.go @@ -0,0 +1,21 @@ +package crosspackage_test + +import ( + "testing" + + "a/crosspackage" +) + +func TestValidate(t *testing.T) { // want `test function "TestValidate" duplicates "TestValidate" for Validate; consolidate scenarios under "TestValidate" using table-driven cases or t.Run subtests` + crosspackage.Validate() +} + +func TestWorker_Run(t *testing.T) { // want `test function "TestWorker_Run" adds another top-level test for Worker.Run while internal test "TestRun" still exists; consolidate both under "TestWorker_Run" using table-driven cases or t.Run subtests` + worker := &crosspackage.Worker{} + worker.Run() +} + +func TestOther_Run(t *testing.T) { + other := &crosspackage.Other{} + other.Run() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/crosspackage/internal_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/crosspackage/internal_test.go new file mode 100644 index 000000000000..526a732afe1c --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/crosspackage/internal_test.go @@ -0,0 +1,12 @@ +package crosspackage + +import "testing" + +func TestValidate(t *testing.T) { + Validate() +} + +func TestRun(t *testing.T) { + worker := &Worker{} + worker.Run() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/crosspackage/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/crosspackage/production.go new file mode 100644 index 000000000000..0dc717738334 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/crosspackage/production.go @@ -0,0 +1,11 @@ +package crosspackage // want package:"testfuncstructure production symbols" + +func Validate() {} + +type Worker struct{} + +func (*Worker) Run() {} + +type Other struct{} + +func (*Other) Run() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/external/external.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/external/external.go index 7a0331a702c5..78cab85eab06 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/external/external.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/external/external.go @@ -1,4 +1,4 @@ -package external +package external // want package:"testfuncstructure production symbols" func Validate() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/generated.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/generated.go index 9c803dc594d2..39e9b1fca27b 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/generated.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/generated.go @@ -1,3 +1,4 @@ +// want package:"testfuncstructure production symbols" // Code generated by test fixture. DO NOT EDIT. package good diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production.go index 6d00a436e956..3e991d1a8927 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production.go @@ -1,3 +1,3 @@ -package legacy +package legacy // want package:"testfuncstructure production symbols" func Reconcile() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacyambiguous/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacyambiguous/production.go index 0082d65671b2..51ce4accaecb 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacyambiguous/production.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacyambiguous/production.go @@ -1,4 +1,4 @@ -package legacyambiguous +package legacyambiguous // want package:"testfuncstructure production symbols" func Alpha() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/precedence/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/precedence/production.go index 4dbbfd0454d7..4316b6407468 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/precedence/production.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/precedence/production.go @@ -1,4 +1,4 @@ -package precedence +package precedence // want package:"testfuncstructure production symbols" func Reconcile() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/provenance/generated.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/provenance/generated.go new file mode 100644 index 000000000000..9e1b6ab49e15 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/provenance/generated.go @@ -0,0 +1,9 @@ +// want package:"testfuncstructure production symbols" +// Code generated by fixture. DO NOT EDIT. + +package provenance + +func (widget *Widget) DeepCopy() *Widget { + copy := *widget + return © +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/provenance/helper_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/provenance/helper_test.go new file mode 100644 index 000000000000..7d944b88e3bc --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/provenance/helper_test.go @@ -0,0 +1,9 @@ +package provenance + +import "testing" + +func ExportedTestHelper(t *testing.T) {} + +type TestOnlyWorker struct{} + +func (*TestOnlyWorker) Run() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/provenance/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/provenance/production.go new file mode 100644 index 000000000000..1b7d92e347f3 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/provenance/production.go @@ -0,0 +1,5 @@ +package provenance + +func Validate() {} + +type Widget struct{} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/provenance/production_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/provenance/production_test.go new file mode 100644 index 000000000000..e024482dda69 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/provenance/production_test.go @@ -0,0 +1,25 @@ +package provenance_test + +import ( + "testing" + + "a/provenance" +) + +func TestValidate(t *testing.T) { + provenance.Validate() +} + +func TestGeneratedBehavior(t *testing.T) { + widget := &provenance.Widget{} + widget.DeepCopy() +} + +func TestHelperBehavior(t *testing.T) { + provenance.ExportedTestHelper(t) +} + +func TestHelperMethodBehavior(t *testing.T) { + worker := &provenance.TestOnlyWorker{} + worker.Run() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/scenarioonly/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/scenarioonly/production.go index ea4b30177ebf..267fd8c89a41 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/scenarioonly/production.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/scenarioonly/production.go @@ -1,3 +1,3 @@ -package scenarioonly +package scenarioonly // want package:"testfuncstructure production symbols" func Reconcile() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/split/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/split/production.go index f78462ae6e4f..8d13c1141470 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/split/production.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/split/production.go @@ -1,4 +1,4 @@ -package split +package split // want package:"testfuncstructure production symbols" func Reconcile() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unrelated/common/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unrelated/common/production.go new file mode 100644 index 000000000000..3d298a28a1b7 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unrelated/common/production.go @@ -0,0 +1,3 @@ +package common // want package:"testfuncstructure production symbols" + +func Validate() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unrelated/common/production_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unrelated/common/production_test.go new file mode 100644 index 000000000000..59fe50d3470e --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unrelated/common/production_test.go @@ -0,0 +1,11 @@ +package common_test + +import ( + "testing" + + "a/unrelated/inner/common" +) + +func TestDependencyBehavior(t *testing.T) { + common.Helper() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unrelated/inner/common/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unrelated/inner/common/production.go new file mode 100644 index 000000000000..0d17756f81cf --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unrelated/inner/common/production.go @@ -0,0 +1,3 @@ +package common + +func Helper() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/support/util/cleanup_tracker_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/support/util/cleanup_tracker_test.go new file mode 100644 index 000000000000..d068f10479b8 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/support/util/cleanup_tracker_test.go @@ -0,0 +1,7 @@ +package util + +import "testing" + +func TestConcurrentAccess(t *testing.T) { // want `test function "TestConcurrentAccess" must be named "TestRecordFailure" to map to RecordFailure` + RecordFailure() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/support/util/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/support/util/production.go new file mode 100644 index 000000000000..e239df83054b --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/support/util/production.go @@ -0,0 +1,3 @@ +package util // want package:"testfuncstructure production symbols" + +func RecordFailure() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/e2e/good/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/e2e/good/production.go index 6ea8574b7ef4..976097af0b29 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/e2e/good/production.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/e2e/good/production.go @@ -1,3 +1,3 @@ -package good +package good // want package:"testfuncstructure production symbols" func Reconcile() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/envtest/good/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/envtest/good/production.go index 6ea8574b7ef4..976097af0b29 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/envtest/good/production.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/envtest/good/production.go @@ -1,3 +1,3 @@ -package good +package good // want package:"testfuncstructure production symbols" func Reconcile() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/integration/good/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/integration/good/production.go index 6ea8574b7ef4..976097af0b29 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/integration/good/production.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/integration/good/production.go @@ -1,3 +1,3 @@ -package good +package good // want package:"testfuncstructure production symbols" func Reconcile() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/reqserving-e2e/good/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/reqserving-e2e/good/production.go index 6ea8574b7ef4..976097af0b29 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/reqserving-e2e/good/production.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/test/reqserving-e2e/good/production.go @@ -1,3 +1,3 @@ -package good +package good // want package:"testfuncstructure production symbols" func Reconcile() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go index 19b2bb017138..e03d0dd9987f 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go @@ -14,13 +14,16 @@ import ( "github.com/openshift/hypershift/hack/tools/hypershiftlinter/analyzers/pathutil" + "k8s.io/apimachinery/pkg/util/sets" + "golang.org/x/tools/go/analysis" ) var Analyzer = &analysis.Analyzer{ - Name: "testfuncstructure", - Doc: "checks that unit tests map one-to-one to production functions and methods", - Run: run, + Name: "testfuncstructure", + Doc: "checks that unit tests map one-to-one to production functions and methods", + Run: run, + FactTypes: []analysis.Fact{new(productionMetadata)}, } type productionSymbol struct { @@ -39,6 +42,9 @@ type testFunction struct { decl *ast.FuncDecl filename string name string + namePos token.Pos + nameEnd token.Pos + imported bool suppressed bool resolution resolution } @@ -67,13 +73,7 @@ type symbolMatch struct { priority int } -var excludedBuildTags = map[string]struct{}{ - "e2e": {}, - "e2ev2": {}, - "envtest": {}, - "integration": {}, - "reqserving": {}, -} +var excludedBuildTags = sets.New("e2e", "e2ev2", "envtest", "integration", "reqserving") func run(pass *analysis.Pass) (any, error) { symbols := collectProductionSymbols(pass) @@ -100,6 +100,9 @@ func run(pass *analysis.Pass) (any, error) { } } } + for _, test := range collectImportedTests(pass, symbolsByID) { + groups[test.resolution.target.id] = append(groups[test.resolution.target.id], test) + } groupIDs := make([]string, 0, len(groups)) for id := range groups { @@ -109,6 +112,9 @@ func run(pass *analysis.Pass) (any, error) { for _, id := range groupIDs { reportGroup(pass, groups[id]) } + if !strings.HasSuffix(pass.Pkg.Name(), "_test") && len(symbols) > 0 { + exportProductionMetadata(pass, symbols, groups) + } return nil, nil } @@ -116,7 +122,16 @@ func run(pass *analysis.Pass) (any, error) { func collectProductionSymbols(pass *analysis.Pass) []*productionSymbol { seen := map[string]*productionSymbol{} if target := externalPackageUnderTest(pass); target != nil { - collectExportedSymbols(target, seen) + var metadata productionMetadata + if pass.ImportPackageFact(target, &metadata) { + collectExportedSymbols(target, seen) + productionIDs := sets.New(metadata.SymbolIDs...) + for id := range seen { + if !productionIDs.Has(id) { + delete(seen, id) + } + } + } } else { for _, file := range pass.Files { filename := pass.Fset.File(file.Pos()).Name() @@ -153,18 +168,10 @@ func externalPackageUnderTest(pass *analysis.Pass) *types.Package { name := strings.TrimSuffix(pass.Pkg.Name(), "_test") wantPath := strings.TrimSuffix(pass.Pkg.Path(), "_test") - var candidates []*types.Package for _, imported := range pass.Pkg.Imports() { - if imported.Name() != name { - continue - } - if imported.Path() == wantPath { + if imported.Name() == name && imported.Path() == wantPath { return imported } - candidates = append(candidates, imported) - } - if len(candidates) == 1 { - return candidates[0] } return nil } @@ -224,27 +231,10 @@ func addProductionSymbol(seen map[string]*productionSymbol, fn *types.Func) { id: id, display: display, canonicalTest: canonical, - variants: deduplicateVariants(variants), + variants: variants, } } -func deduplicateVariants(variants []testNameVariant) []testNameVariant { - result := make([]testNameVariant, 0, len(variants)) - for _, candidate := range variants { - duplicate := false - for _, existing := range result { - if candidate.name == existing.name { - duplicate = true - break - } - } - if !duplicate { - result = append(result, candidate) - } - } - return result -} - func receiverName(fn *types.Func) string { signature, ok := fn.Type().(*types.Signature) if !ok || signature.Recv() == nil { @@ -293,6 +283,8 @@ func collectTests(pass *analysis.Pass) []*testFunction { decl: fn, filename: filename, name: fn.Name.Name, + namePos: fn.Name.Pos(), + nameEnd: fn.Name.End(), suppressed: hasNoLintDirective(fn), }) } @@ -301,7 +293,7 @@ func collectTests(pass *analysis.Pass) []*testFunction { if result := strings.Compare(a.filename, b.filename); result != 0 { return result } - return cmpPos(a.decl.Pos(), b.decl.Pos()) + return cmpPos(a.namePos, b.namePos) }) return tests } @@ -350,24 +342,20 @@ func hasExcludedBuildTag(file *ast.File) bool { } func requiresExcludedBuildTag(expression constraint.Expr) bool { - otherTags := map[string]struct{}{} + otherTags := sets.New[string]() collectOtherBuildTags(expression, otherTags) if len(otherTags) > 16 { return false } - tags := make([]string, 0, len(otherTags)) - for tag := range otherTags { - tags = append(tags, tag) - } - slices.Sort(tags) + tags := sets.List(otherTags) for assignment := range 1 << len(tags) { values := make(map[string]bool, len(tags)) for index, tag := range tags { values[tag] = assignment&(1< current.priority } -func resolveMatches(matches []symbolMatch, called map[string]struct{}) resolution { +func resolveMatches(matches []symbolMatch, called sets.Set[string]) resolution { if len(matches) == 1 { match := matches[0] return resolution{kind: match.kind, target: match.symbol, priority: match.priority} } calledMatches := slices.DeleteFunc(slices.Clone(matches), func(match symbolMatch) bool { - _, ok := called[match.symbol.id] - return !ok + return !called.Has(match.symbol.id) }) if len(calledMatches) == 1 { match := calledMatches[0] @@ -575,8 +562,8 @@ func resolveMatches(matches []symbolMatch, called map[string]struct{}) resolutio return resolution{kind: resolutionAmbiguous, candidates: candidates} } -func calledProductionSymbols(pass *analysis.Pass, test *ast.FuncDecl, symbolsByID map[string]*productionSymbol) map[string]struct{} { - called := map[string]struct{}{} +func calledProductionSymbols(pass *analysis.Pass, test *ast.FuncDecl, symbolsByID map[string]*productionSymbol) sets.Set[string] { + called := sets.New[string]() ast.Inspect(test.Body, func(node ast.Node) bool { call, ok := node.(*ast.CallExpr) if !ok { @@ -588,7 +575,7 @@ func calledProductionSymbols(pass *analysis.Pass, test *ast.FuncDecl, symbolsByI } id := symbolID(fn) if _, ok := symbolsByID[id]; ok { - called[id] = struct{}{} + called.Insert(id) } return true }) @@ -627,8 +614,8 @@ func reportAmbiguous(pass *analysis.Pass, test *testFunction) { names = append(names, candidate.display) } pass.Report(analysis.Diagnostic{ - Pos: test.decl.Name.Pos(), - End: test.decl.Name.End(), + Pos: test.namePos, + End: test.nameEnd, Message: fmt.Sprintf("test function %q does not map unambiguously to one production function (candidates: %s); use the canonical Test or Test_ form, or add a documented //nolint:hypershiftlinter exception", test.name, strings.Join(names, ", ")), }) } @@ -646,10 +633,16 @@ func reportGroup(pass *analysis.Pass, tests []*testFunction) { if a.resolution.priority != b.resolution.priority { return b.resolution.priority - a.resolution.priority } + if a.imported != b.imported { + if a.imported { + return -1 + } + return 1 + } if result := strings.Compare(a.filename, b.filename); result != 0 { return result } - return cmpPos(a.decl.Pos(), b.decl.Pos()) + return cmpPos(a.namePos, b.namePos) }) symbol := tests[0].resolution.target @@ -659,20 +652,20 @@ func reportGroup(pass *analysis.Pass, tests []*testFunction) { primary = tests[0] preferred = primary.name } - if primary != nil && !isLegacyException(pass, primary) && !primary.suppressed { + if primary != nil && !shouldSuppress(pass, primary) { for _, test := range tests[1:] { - if !isLegacyException(pass, test) { + if !test.imported && !isLegacyException(pass, test) { continue } + kind := "legacy" + if test.imported { + kind = "internal" + } pass.Report(analysis.Diagnostic{ - Pos: primary.decl.Name.Pos(), - End: primary.decl.Name.End(), - Message: fmt.Sprintf("test function %q adds another top-level test for %s while legacy test %q still exists; consolidate both under %q using table-driven cases or t.Run subtests", primary.name, symbol.display, test.name, preferred), - Related: []analysis.RelatedInformation{{ - Pos: test.decl.Name.Pos(), - End: test.decl.Name.End(), - Message: fmt.Sprintf("legacy top-level test %q", test.name), - }}, + Pos: primary.namePos, + End: primary.nameEnd, + Message: fmt.Sprintf("test function %q adds another top-level test for %s while %s test %q still exists; consolidate both under %q using table-driven cases or t.Run subtests", primary.name, symbol.display, kind, test.name, preferred), + Related: relatedTest(test, fmt.Sprintf("%s top-level test %q", kind, test.name)), }) break } @@ -703,31 +696,37 @@ func reportGroup(pass *analysis.Pass, tests []*testFunction) { } diagnostic := analysis.Diagnostic{ - Pos: test.decl.Name.Pos(), - End: test.decl.Name.End(), + Pos: test.namePos, + End: test.nameEnd, Message: message, } if primary != nil && primary != test { - diagnostic.Related = []analysis.RelatedInformation{{ - Pos: primary.decl.Name.Pos(), - End: primary.decl.Name.End(), - Message: fmt.Sprintf("existing top-level test %q", primary.name), - }} + diagnostic.Related = relatedTest(primary, fmt.Sprintf("existing top-level test %q", primary.name)) } pass.Report(diagnostic) } } func shouldSuppress(pass *analysis.Pass, test *testFunction) bool { - if test.suppressed { + if test.imported || test.suppressed { return true } return isLegacyException(pass, test) } +func relatedTest(test *testFunction, message string) []analysis.RelatedInformation { + if !test.namePos.IsValid() { + return nil + } + return []analysis.RelatedInformation{{ + Pos: test.namePos, + End: test.nameEnd, + Message: message, + }} +} + func isLegacyException(pass *analysis.Pass, test *testFunction) bool { - _, ok := legacyExceptions[legacyKey(pass, test)] - return ok + return legacyExceptions.Has(legacyKey(pass, test)) } func legacyKey(pass *analysis.Pass, test *testFunction) string { diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go index c44010c07e62..371a222b418b 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go @@ -5,6 +5,8 @@ import ( "go/token" "testing" + . "github.com/onsi/gomega" + "golang.org/x/tools/go/analysis/analysistest" ) @@ -14,25 +16,54 @@ func TestRun(t *testing.T) { "a/legacyambiguous/production_test.go|TestWorkflow", } for _, fixture := range legacyFixtures { - legacyExceptions[fixture] = struct{}{} - defer delete(legacyExceptions, fixture) + legacyExceptions.Insert(fixture) + defer legacyExceptions.Delete(fixture) } testdata := analysistest.TestData() - analysistest.Run(t, testdata, Analyzer, - "a/good", - "a/split", - "a/ambiguous", - "a/external", - "a/legacy", - "a/legacyambiguous", - "a/precedence", - "a/scenarioonly", - "test/e2e/good", - "test/envtest/good", - "test/integration/good", - "test/reqserving-e2e/good", - ) + t.Run("When unit tests are analyzed, it should distinguish supported mappings and structure violations", func(t *testing.T) { + analysistest.Run(t, testdata, Analyzer, + "a/good", + "a/split", + "a/ambiguous", + "a/external", + "a/legacy", + "a/legacyambiguous", + "a/precedence", + "a/scenarioonly", + "test/e2e/good", + "test/envtest/good", + "test/integration/good", + "test/reqserving-e2e/good", + ) + }) + t.Run("When external tests import only a namesake dependency, it should ignore that dependency", func(t *testing.T) { + analysistest.Run(t, testdata, Analyzer, "a/unrelated/common") + }) + t.Run("When external tests call helpers or generated code, it should not map those calls to production targets", func(t *testing.T) { + analysistest.Run(t, testdata, Analyzer, "a/provenance") + }) + t.Run("When internal and external tests share production targets, it should report each external duplicate with the internal location", func(t *testing.T) { + g := NewWithT(t) + results := analysistest.Run(t, testdata, Analyzer, "a/crosspackage") + foundExternalPackage := false + for _, result := range results { + if result.Pass.Pkg.Name() != "crosspackage_test" { + continue + } + foundExternalPackage = true + g.Expect(result.Diagnostics).To(HaveLen(2)) + for _, diagnostic := range result.Diagnostics { + g.Expect(diagnostic.Related).To(HaveLen(1)) + position := result.Pass.Fset.Position(diagnostic.Related[0].Pos) + g.Expect(position.Filename).To(HaveSuffix("internal_test.go")) + } + } + g.Expect(foundExternalPackage).To(BeTrue()) + }) + t.Run("When a new test reuses a removed baseline path and name, it should still report the violation", func(t *testing.T) { + analysistest.Run(t, testdata, Analyzer, "support/util") + }) } func TestHasExcludedBuildTag(t *testing.T) { diff --git a/support/util/loglevel_test.go b/support/util/loglevel_test.go index fb56054afd52..be855b0108ec 100644 --- a/support/util/loglevel_test.go +++ b/support/util/loglevel_test.go @@ -51,10 +51,9 @@ func TestLogLevelToKlogVerbosity(t *testing.T) { func TestLogLevelToEtcdLevel(t *testing.T) { tests := []struct { - name string - level hyperv1.LogLevel - expected string - expectPanic bool + name string + level hyperv1.LogLevel + expected string }{ { name: "When LogLevel is empty, it should return etcd level info", @@ -71,28 +70,38 @@ func TestLogLevelToEtcdLevel(t *testing.T) { level: hyperv1.Debug, expected: "debug", }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + g.Expect(LogLevelToEtcdLevel(tt.level)).To(Equal(tt.expected)) + }) + } +} + +func TestLogLevelToEtcdLevelPanicsOnUnsupported(t *testing.T) { + // Trace and TraceAll are valid LogLevel values but are not supported by etcd + // and are rejected at the API level (CEL). If one ever reaches this mapping + // (e.g. via a non-CRD path), it must panic rather than silently degrade. + tests := []struct { + name string + level hyperv1.LogLevel + }{ { - name: "When LogLevel is Trace, it should panic", - level: hyperv1.Trace, - expectPanic: true, + name: "When LogLevel is Trace, it should panic", + level: hyperv1.Trace, }, { - name: "When LogLevel is TraceAll, it should panic", - level: hyperv1.TraceAll, - expectPanic: true, + name: "When LogLevel is TraceAll, it should panic", + level: hyperv1.TraceAll, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - if tt.expectPanic { - // Trace levels are rejected by etcd's API-level validation. Panic if - // one reaches this mapping through a non-CRD path. - g.Expect(func() { LogLevelToEtcdLevel(tt.level) }).To(Panic()) - return - } - g.Expect(LogLevelToEtcdLevel(tt.level)).To(Equal(tt.expected)) + g.Expect(func() { LogLevelToEtcdLevel(tt.level) }).To(Panic()) }) } } From 91ba8240daef450b191e3c685c777ca44b4444ba Mon Sep 17 00:00:00 2001 From: Bryan Cox Date: Wed, 30 Sep 2026 18:42:30 -0400 Subject: [PATCH 5/8] fix(lint): honor cross-package test exceptions Do not use an explicitly suppressed internal scenario as a duplicate witness against an external canonical test. Preserve the distinct legacy-baseline protections and cover both behaviors with regression fixtures. Complete the exact baseline for pre-existing canonical declarations exposed by baselined ambiguous tests. Signed-off-by: Bryan Cox Assisted-by: LLM --- .../analyzers/testfuncstructure/legacy.go | 4 ++++ .../testdata/src/a/externalexception/external_test.go | 11 +++++++++++ .../testdata/src/a/externalexception/internal_test.go | 8 ++++++++ .../testdata/src/a/externalexception/production.go | 3 +++ .../testdata/src/a/externallegacy/external_test.go | 11 +++++++++++ .../testdata/src/a/externallegacy/internal_test.go | 7 +++++++ .../testdata/src/a/externallegacy/production.go | 3 +++ .../analyzers/testfuncstructure/testfuncstructure.go | 3 +++ .../testfuncstructure/testfuncstructure_test.go | 7 +++++++ 9 files changed, 57 insertions(+) create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/external_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/internal_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/external_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/internal_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/production.go diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go index 3c54fe65072a..0e6646ef01f0 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go @@ -63,6 +63,7 @@ var legacyExceptions = sets.Set[string]{ "cmd/install/assets/hypershift_operator_test.go|TestHyperShiftOperatorDeployment_Build_WorkloadIdentityLabel": {}, "cmd/install/assets/resource_requirements_test.go|TestExternalDNSDeployment_HasResourceRequests": {}, "cmd/install/assets/resource_requirements_test.go|TestHyperShiftOperatorDeployment_HasResourceRequests": {}, + "cmd/install/install_test.go|TestApplyDefaults": {}, "cmd/install/install_test.go|TestCRDIncludeFilter": {}, "cmd/install/install_test.go|TestHyperShiftOperatorManifests_SharedIngress": {}, "cmd/install/install_test.go|TestHyperShiftOperatorManifests_WebhookFlags": {}, @@ -647,7 +648,9 @@ var legacyExceptions = sets.Set[string]{ "hypershift-operator/controllers/nodepool/nto_test.go|TestSetPerformanceProfileStatus": {}, "hypershift-operator/controllers/nodepool/openstack/openstack_test.go|TestClusterImageName": {}, "hypershift-operator/controllers/nodepool/openstack/openstack_test.go|TestOpenStackMachineTemplate": {}, + "hypershift-operator/controllers/nodepool/secret_janitor_test.go|TestSecretJanitor_Reconcile": {}, "hypershift-operator/controllers/nodepool/token_test.go|TestOutdatedUserdataSecret": {}, + "hypershift-operator/controllers/nodepool/token_test.go|TestTokenReconcile": {}, "hypershift-operator/controllers/nodepool/token_test.go|TestTokenUserDataSecret": {}, "hypershift-operator/controllers/nodepool/tracing_test.go|TestNodePoolReconcileTracingDeletingNodePool": {}, "hypershift-operator/controllers/nodepool/tracing_test.go|TestNodePoolReconcileTracingNotFoundSkipsSpan": {}, @@ -741,6 +744,7 @@ var legacyExceptions = sets.Set[string]{ "karpenter-operator/controllers/nodeclass/ec2_nodeclass_controller_test.go|TestReconcileStatusTagConflictCondition": {}, "pkg/featuregates/featuregates_test.go|TestConfiguringUnknownFeatureSetErrors": {}, "pkg/featuregates/featuregates_test.go|TestCreatingFeatureGates": {}, + "product-cli/cmd/kubeconfig/create_test.go|TestNewCreateCommand": {}, "product-cli/cmd/kubeconfig/create_test.go|TestNewCreateCommandRunE": {}, "product-cli/cmd/nodepool/agent/create_test.go|TestNewCreateCommand": {}, "product-cli/cmd/nodepool/agent/create_test.go|TestUpdateNodePool": {}, diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/external_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/external_test.go new file mode 100644 index 000000000000..2c6cb5b574b4 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/external_test.go @@ -0,0 +1,11 @@ +package externalexception_test + +import ( + "testing" + + "a/externalexception" +) + +func TestValidate(t *testing.T) { + externalexception.Validate() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/internal_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/internal_test.go new file mode 100644 index 000000000000..43aab51eda68 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/internal_test.go @@ -0,0 +1,8 @@ +package externalexception + +import "testing" + +//nolint:hypershiftlinter // testfuncstructure: validates compatibility across package boundaries +func TestValidateCompatibility(t *testing.T) { + Validate() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/production.go new file mode 100644 index 000000000000..ce965704c611 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/production.go @@ -0,0 +1,3 @@ +package externalexception // want package:"testfuncstructure production symbols" + +func Validate() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/external_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/external_test.go new file mode 100644 index 000000000000..1e8aeaabf55e --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/external_test.go @@ -0,0 +1,11 @@ +package externallegacy_test + +import ( + "testing" + + "a/externallegacy" +) + +func TestValidate(t *testing.T) { // want `test function "TestValidate" adds another top-level test for Validate while internal test "TestValidateErrors" still exists` + externallegacy.Validate() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/internal_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/internal_test.go new file mode 100644 index 000000000000..21863d4a10d2 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/internal_test.go @@ -0,0 +1,7 @@ +package externallegacy + +import "testing" + +func TestValidateErrors(t *testing.T) { + Validate() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/production.go new file mode 100644 index 000000000000..31fc30e20334 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/production.go @@ -0,0 +1,3 @@ +package externallegacy // want package:"testfuncstructure production symbols" + +func Validate() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go index e03d0dd9987f..665189cd8e7b 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go @@ -654,6 +654,9 @@ func reportGroup(pass *analysis.Pass, tests []*testFunction) { } if primary != nil && !shouldSuppress(pass, primary) { for _, test := range tests[1:] { + if test.imported && test.suppressed { + continue + } if !test.imported && !isLegacyException(pass, test) { continue } diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go index 371a222b418b..f1841d5df421 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go @@ -14,6 +14,7 @@ func TestRun(t *testing.T) { legacyFixtures := []string{ "a/legacy/production_test.go|TestReconcileErrors", "a/legacyambiguous/production_test.go|TestWorkflow", + "a/externallegacy/internal_test.go|TestValidateErrors", } for _, fixture := range legacyFixtures { legacyExceptions.Insert(fixture) @@ -64,6 +65,12 @@ func TestRun(t *testing.T) { t.Run("When a new test reuses a removed baseline path and name, it should still report the violation", func(t *testing.T) { analysistest.Run(t, testdata, Analyzer, "support/util") }) + t.Run("When an internal scenario has an explicit exception, it should allow an external canonical test", func(t *testing.T) { + analysistest.Run(t, testdata, Analyzer, "a/externalexception") + }) + t.Run("When an internal scenario is only baselined, it should still reject an additional external test", func(t *testing.T) { + analysistest.Run(t, testdata, Analyzer, "a/externallegacy") + }) } func TestHasExcludedBuildTag(t *testing.T) { From 04d33643dab7464fdb4e26b6909a3d500ef08000 Mon Sep 17 00:00:00 2001 From: Bryan Cox Date: Tue, 6 Oct 2026 11:36:42 -0400 Subject: [PATCH 6/8] fix(lint): handle explicit test exceptions and testing.T aliases Exclude explicitly suppressed tests from duplicate accounting while preserving legacy baseline protections and exported suppression metadata. Resolve aliases when checking testing.T parameters, and cover same-package and cross-package exceptions, baselines, and direct/chained aliases with fixtures. Signed-off-by: Bryan Cox Assisted-by: LLM --- .../testdata/src/a/exception/production.go | 7 ++++++ .../src/a/exception/production_test.go | 24 +++++++++++++++++++ .../src/a/externalexception/external_test.go | 10 ++++++++ .../src/a/externalexception/internal_test.go | 6 +++++ .../src/a/externalexception/production.go | 4 ++++ .../src/a/externallegacy/external_test.go | 5 ++++ .../src/a/externallegacy/internal_test.go | 5 ++++ .../src/a/externallegacy/production.go | 4 ++++ .../testdata/src/a/legacy/production.go | 4 ++++ .../testdata/src/a/legacy/production_test.go | 10 ++++++++ .../src/a/testingalias/external_test.go | 18 ++++++++++++++ .../testdata/src/a/testingalias/production.go | 3 +++ .../src/a/testingalias/production_test.go | 13 ++++++++++ .../testfuncstructure/testfuncstructure.go | 17 +++++++++---- .../testfuncstructure_test.go | 15 ++++++++++-- 15 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/exception/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/exception/production_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/testingalias/external_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/testingalias/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/testingalias/production_test.go diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/exception/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/exception/production.go new file mode 100644 index 000000000000..b9b4cc5ab42b --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/exception/production.go @@ -0,0 +1,7 @@ +package exception // want package:"testfuncstructure production symbols" + +type Worker struct{} + +func (*Worker) Run() {} + +func Validate() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/exception/production_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/exception/production_test.go new file mode 100644 index 000000000000..a0f1468c2522 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/exception/production_test.go @@ -0,0 +1,24 @@ +package exception + +import "testing" + +//nolint:hypershiftlinter // testfuncstructure: validates compatibility independently of the standard method test +func TestWorker_Run(t *testing.T) { + worker := &Worker{} + worker.Run() +} + +func TestWorkerRun(t *testing.T) { + worker := &Worker{} + worker.Run() +} + +func TestRun(t *testing.T) { // want `test function "TestRun" duplicates "TestWorkerRun" for Worker.Run; consolidate scenarios under "TestWorkerRun" using table-driven cases or t.Run subtests` + worker := &Worker{} + worker.Run() +} + +//nolint:hypershiftlinter // testfuncstructure: validates compatibility without an ordinary unit test +func TestValidate(t *testing.T) { + Validate() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/external_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/external_test.go index 2c6cb5b574b4..44efef6155fb 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/external_test.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/external_test.go @@ -9,3 +9,13 @@ import ( func TestValidate(t *testing.T) { externalexception.Validate() } + +func TestWorker_Run(t *testing.T) { + worker := &externalexception.Worker{} + worker.Run() +} + +func TestWorkerRun(t *testing.T) { // want `test function "TestWorkerRun" duplicates "TestWorker_Run" for Worker.Run; consolidate scenarios under "TestWorker_Run" using table-driven cases or t.Run subtests` + worker := &externalexception.Worker{} + worker.Run() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/internal_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/internal_test.go index 43aab51eda68..4ee9b641fc01 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/internal_test.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/internal_test.go @@ -6,3 +6,9 @@ import "testing" func TestValidateCompatibility(t *testing.T) { Validate() } + +//nolint:hypershiftlinter // testfuncstructure: validates internal compatibility independently of the external contract +func TestWorker_Run(t *testing.T) { + worker := &Worker{} + worker.Run() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/production.go index ce965704c611..a2373bdfc5b1 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/production.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externalexception/production.go @@ -1,3 +1,7 @@ package externalexception // want package:"testfuncstructure production symbols" func Validate() {} + +type Worker struct{} + +func (*Worker) Run() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/external_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/external_test.go index 1e8aeaabf55e..67618372d29b 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/external_test.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/external_test.go @@ -9,3 +9,8 @@ import ( func TestValidate(t *testing.T) { // want `test function "TestValidate" adds another top-level test for Validate while internal test "TestValidateErrors" still exists` externallegacy.Validate() } + +func TestWorker_Run(t *testing.T) { // want `test function "TestWorker_Run" duplicates "TestWorker_Run" for Worker.Run; consolidate scenarios under "TestWorker_Run" using table-driven cases or t.Run subtests` + worker := &externallegacy.Worker{} + worker.Run() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/internal_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/internal_test.go index 21863d4a10d2..5984fd2c85b9 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/internal_test.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/internal_test.go @@ -5,3 +5,8 @@ import "testing" func TestValidateErrors(t *testing.T) { Validate() } + +func TestWorker_Run(t *testing.T) { + worker := &Worker{} + worker.Run() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/production.go index 31fc30e20334..5845bb642066 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/production.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/externallegacy/production.go @@ -1,3 +1,7 @@ package externallegacy // want package:"testfuncstructure production symbols" func Validate() {} + +type Worker struct{} + +func (*Worker) Run() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production.go index 3e991d1a8927..94e7238c6dcd 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production.go @@ -1,3 +1,7 @@ package legacy // want package:"testfuncstructure production symbols" func Reconcile() {} + +type Worker struct{} + +func (*Worker) Run() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production_test.go index abd71f17744e..3eaaf0ddf4e3 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production_test.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/legacy/production_test.go @@ -9,3 +9,13 @@ func TestReconcile(t *testing.T) { // want `test function "TestReconcile" adds a func TestReconcileErrors(t *testing.T) { Reconcile() } + +func TestWorker_Run(t *testing.T) { + worker := &Worker{} + worker.Run() +} + +func TestWorkerRun(t *testing.T) { // want `test function "TestWorkerRun" duplicates "TestWorker_Run" for Worker.Run; consolidate scenarios under "TestWorker_Run" using table-driven cases or t.Run subtests` + worker := &Worker{} + worker.Run() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/testingalias/external_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/testingalias/external_test.go new file mode 100644 index 000000000000..359f0b4d7473 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/testingalias/external_test.go @@ -0,0 +1,18 @@ +package testingalias_test + +import ( + "testing" + + "a/testingalias" +) + +type testingT = testing.T +type T = testingT + +func TestReconcile(t *T) { // want `test function "TestReconcile" duplicates "TestReconcile" for Reconcile; consolidate scenarios under "TestReconcile" using table-driven cases or t.Run subtests` + testingalias.Reconcile() +} + +func TestReconcileErrors(t *T) { // want `test function "TestReconcileErrors" also tests Reconcile; consolidate it into "TestReconcile" using table-driven cases or t.Run subtests` + testingalias.Reconcile() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/testingalias/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/testingalias/production.go new file mode 100644 index 000000000000..4605a66a5e8c --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/testingalias/production.go @@ -0,0 +1,3 @@ +package testingalias // want package:"testfuncstructure production symbols" + +func Reconcile() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/testingalias/production_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/testingalias/production_test.go new file mode 100644 index 000000000000..558684ee9d3d --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/testingalias/production_test.go @@ -0,0 +1,13 @@ +package testingalias + +import "testing" + +type T = testing.T + +func TestReconcile(t *T) { + Reconcile() +} + +func TestReconcileErrors(t *T) { // want `test function "TestReconcileErrors" also tests Reconcile; consolidate it into "TestReconcile" using table-driven cases or t.Run subtests` + Reconcile() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go index 665189cd8e7b..ca2b04055931 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go @@ -405,11 +405,11 @@ func isOrdinaryTest(pass *analysis.Pass, fn *ast.FuncDecl) bool { } func isTestingT(typ types.Type) bool { - pointer, ok := typ.(*types.Pointer) + pointer, ok := types.Unalias(typ).(*types.Pointer) if !ok { return false } - named, ok := pointer.Elem().(*types.Named) + named, ok := types.Unalias(pointer.Elem()).(*types.Named) return ok && named.Obj().Pkg() != nil && named.Obj().Pkg().Path() == "testing" && named.Obj().Name() == "T" } @@ -621,6 +621,16 @@ func reportAmbiguous(pass *analysis.Pass, test *testFunction) { } func reportGroup(pass *analysis.Pass, tests []*testFunction) { + // Explicit exceptions do not reserve a production target, but legacy baseline + // tests still count so new tests cannot expand existing violations. Clone the + // group to preserve suppression metadata for external test packages. + tests = slices.DeleteFunc(slices.Clone(tests), func(test *testFunction) bool { + return test.suppressed + }) + if len(tests) == 0 { + return + } + slices.SortFunc(tests, func(a, b *testFunction) int { if a.resolution.kind != b.resolution.kind { if a.resolution.kind == resolutionExact { @@ -654,9 +664,6 @@ func reportGroup(pass *analysis.Pass, tests []*testFunction) { } if primary != nil && !shouldSuppress(pass, primary) { for _, test := range tests[1:] { - if test.imported && test.suppressed { - continue - } if !test.imported && !isLegacyException(pass, test) { continue } diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go index f1841d5df421..1f25f73aef38 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go @@ -13,8 +13,13 @@ import ( func TestRun(t *testing.T) { legacyFixtures := []string{ "a/legacy/production_test.go|TestReconcileErrors", + "a/legacy/production_test.go|TestWorker_Run", "a/legacyambiguous/production_test.go|TestWorkflow", "a/externallegacy/internal_test.go|TestValidateErrors", + "a/externallegacy/internal_test.go|TestWorker_Run", + // Explicit exceptions must take precedence over baseline accounting. + "a/exception/production_test.go|TestWorker_Run", + "a/externalexception/internal_test.go|TestWorker_Run", } for _, fixture := range legacyFixtures { legacyExceptions.Insert(fixture) @@ -65,12 +70,18 @@ func TestRun(t *testing.T) { t.Run("When a new test reuses a removed baseline path and name, it should still report the violation", func(t *testing.T) { analysistest.Run(t, testdata, Analyzer, "support/util") }) - t.Run("When an internal scenario has an explicit exception, it should allow an external canonical test", func(t *testing.T) { + t.Run("When internal tests have explicit exceptions, it should allow an external canonical test and report remaining duplicates", func(t *testing.T) { analysistest.Run(t, testdata, Analyzer, "a/externalexception") }) - t.Run("When an internal scenario is only baselined, it should still reject an additional external test", func(t *testing.T) { + t.Run("When canonical tests have explicit exceptions, it should exclude them from duplicate accounting", func(t *testing.T) { + analysistest.Run(t, testdata, Analyzer, "a/exception") + }) + t.Run("When internal tests are only baselined, it should still reject additional external tests", func(t *testing.T) { analysistest.Run(t, testdata, Analyzer, "a/externallegacy") }) + t.Run("When tests use direct and chained testing.T aliases, it should still enforce one top-level test per production function", func(t *testing.T) { + analysistest.Run(t, testdata, Analyzer, "a/testingalias") + }) } func TestHasExcludedBuildTag(t *testing.T) { From 02f33cc6a441625c018a6bb2547d7636d1abc35b Mon Sep 17 00:00:00 2001 From: Bryan Cox Date: Tue, 27 Oct 2026 15:26:22 -0400 Subject: [PATCH 7/8] fix(lint): resolve test structure review findings Refresh the baseline against main, report internal canonical tests that expand baselined external scenarios, and match called production declarations by types.Func identity. Update generated ownership metadata for the linter testdata. Signed-off-by: Bryan Cox --- .../analyzers/testfuncstructure/legacy.go | 173 ++++++++++++++---- .../testdata/src/a/identity/production.go | 7 + .../src/a/identity/production_test.go | 12 ++ .../src/a/reversecross/external_test.go | 11 ++ .../src/a/reversecross/internal_test.go | 7 + .../testdata/src/a/reversecross/production.go | 3 + .../testfuncstructure/testfuncstructure.go | 55 ++++-- .../testfuncstructure_test.go | 78 ++++++++ skills/restructure-commits/ownership.json | 3 + 9 files changed, 302 insertions(+), 47 deletions(-) create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/identity/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/identity/production_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/reversecross/external_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/reversecross/internal_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/reversecross/production.go diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go index 0e6646ef01f0..d60ffa52b818 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go @@ -4,10 +4,14 @@ import "k8s.io/apimachinery/pkg/util/sets" // legacyExceptions contains exact pre-existing test declarations that do not // satisfy TESTING.md. Remove an entry when the corresponding test is migrated. +// Refreshed against main at 43bbac68e4b28539ae44e6ce4e82eb13518c2b05. var legacyExceptions = sets.Set[string]{ "api/util/ipnet/ipnet_test.go|TestUnmarshal": {}, "cmd/cluster/agent/create_test.go|TestCreateCluster": {}, "cmd/cluster/aws/create_test.go|TestCreateCluster": {}, + "cmd/cluster/aws/destroy_test.go|TestNewDestroyCommandClientProvider": {}, + "cmd/cluster/aws/destroy_test.go|TestValidateCredentialInfo": {}, + "cmd/cluster/aws/destroy_test.go|TestValidateCredentialInfoWithoutClient": {}, "cmd/cluster/azure/create_test.go|TestCreateCluster": {}, "cmd/cluster/azure/create_test.go|TestCreateClusterAutoNodeRequiresKarpenterClientID": {}, "cmd/cluster/azure/create_test.go|TestCredentialSecretHasDeleteWithClusterLabel": {}, @@ -17,11 +21,14 @@ var legacyExceptions = sets.Set[string]{ "cmd/cluster/azure/create_test.go|TestValidateOAuthPublishingStrategy": {}, "cmd/cluster/azure/destroy_test.go|TestDestroyClusterSetsCloudFromHostedCluster": {}, "cmd/cluster/azure/destroy_test.go|TestDestroyClusterSetsGracePeriodFromTopology": {}, + "cmd/cluster/azure/destroy_test.go|TestNewDestroyCommandClientProvider": {}, "cmd/cluster/core/create_test.go|TestAllocateNodeCIDRsFlag": {}, + "cmd/cluster/core/create_test.go|TestCachedClientProvider": {}, "cmd/cluster/core/create_test.go|TestDisableMultiNetworkFlag": {}, "cmd/cluster/core/create_test.go|TestGetServicePublishingStrategyMapping": {}, "cmd/cluster/core/create_test.go|TestOVNKubernetesMTUFlag": {}, "cmd/cluster/core/create_test.go|TestPrototypeResources": {}, + "cmd/cluster/core/destroy_test.go|TestDestroyCluster": {}, "cmd/cluster/core/destroy_test.go|TestForceRemoveAllFinalizers": {}, "cmd/cluster/core/destroy_test.go|TestForceRemoveAllFinalizersErrors": {}, "cmd/cluster/gcp/create_test.go|TestCreateCluster": {}, @@ -30,7 +37,15 @@ var legacyExceptions = sets.Set[string]{ "cmd/cluster/none/create_test.go|TestCreateCluster": {}, "cmd/cluster/openstack/create_test.go|TestCreateCluster": {}, "cmd/cluster/openstack/create_test.go|TestCreateOptions_Validate": {}, + "cmd/cluster/openstack/destroy_test.go|TestNewDestroyCommand": {}, + "cmd/cluster/openstack/destroy_test.go|TestNewDestroyCommandClientProvider": {}, "cmd/cluster/powervs/create_test.go|TestCreateCluster": {}, + "cmd/cluster/powervs/destroy_test.go|TestNewDestroyCommandClientProvider": {}, + "cmd/consolelogs/azure/api_journals_test.go|TestAPIJournalCollectorForNodes": {}, + "cmd/consolelogs/azure/collection_test.go|TestSDKComputeClientBootDiagnosticsState": {}, + "cmd/consolelogs/azure/getlogs_test.go|TestSDKComputeClientRetrieveSerialConsoleLogURI": {}, + "cmd/consolelogs/azure/journals_test.go|TestBoundedJournalWriter": {}, + "cmd/consolelogs/azure/journals_test.go|TestSSHAddress": {}, "cmd/fix/dr_oidc_iam_test.go|TestDrOidcIamOptions_Defaults": {}, "cmd/fix/dr_oidc_iam_test.go|TestDrOidcIamOptions_RunDryRun": {}, "cmd/fix/dr_oidc_iam_test.go|TestOidcDiscoveryURL": {}, @@ -57,12 +72,17 @@ var legacyExceptions = sets.Set[string]{ "cmd/infra/gcp/destroy_iam_test.go|TestDestroyIAMNewDestroyIAMCommand": {}, "cmd/install/assets/crds/assets_test.go|TestCapiResources": {}, "cmd/install/assets/crds/assets_test.go|TestCustomResourceDefinitionsStorageVersion": {}, + "cmd/install/assets/crds/assets_test.go|TestGetCustomResourceDefinition": {}, "cmd/install/assets/hypershift_operator_test.go|TestExternalDNSDeployment_Build": {}, "cmd/install/assets/hypershift_operator_test.go|TestHyperShiftOperatorClusterRole_WebhookRBAC": {}, "cmd/install/assets/hypershift_operator_test.go|TestHyperShiftOperatorDeployment_Build": {}, "cmd/install/assets/hypershift_operator_test.go|TestHyperShiftOperatorDeployment_Build_WorkloadIdentityLabel": {}, "cmd/install/assets/resource_requirements_test.go|TestExternalDNSDeployment_HasResourceRequests": {}, "cmd/install/assets/resource_requirements_test.go|TestHyperShiftOperatorDeployment_HasResourceRequests": {}, + "cmd/install/client_test.go|TestApplyWithNilClient": {}, + "cmd/install/client_test.go|TestDryRunValidateCRDsWithNilClient": {}, + "cmd/install/client_test.go|TestWaitUntilAvailableWithNilClient": {}, + "cmd/install/client_test.go|TestWaitUntilEstablishedWithNilClient": {}, "cmd/install/install_test.go|TestApplyDefaults": {}, "cmd/install/install_test.go|TestCRDIncludeFilter": {}, "cmd/install/install_test.go|TestHyperShiftOperatorManifests_SharedIngress": {}, @@ -71,6 +91,7 @@ var legacyExceptions = sets.Set[string]{ "cmd/install/install_test.go|TestNewInstallOptionsWithDefaults": {}, "cmd/install/install_test.go|TestNewInstallOptionsWithDefaults_InstallScope": {}, "cmd/install/install_test.go|TestOutputsHelpers": {}, + "cmd/install/install_test.go|TestRenderHyperShiftOperator": {}, "cmd/install/install_test.go|TestRenderHyperShiftOperator_RenderSensitive": {}, "cmd/install/install_test.go|TestRenderOutputsScope": {}, "cmd/install/install_test.go|TestSetupCRDs": {}, @@ -84,6 +105,7 @@ var legacyExceptions = sets.Set[string]{ "cmd/nodepool/azure/create_test.go|TestAzureBoundaryConditions": {}, "cmd/nodepool/azure/create_test.go|TestCreateNodePool_When_flags_are_parsed_it_should_generate_correct_nodepool": {}, "cmd/nodepool/azure/create_test.go|TestNodePoolPlatformImageGeneration": {}, + "cmd/nodepool/azure/create_test.go|TestValidateIPForwarding": {}, "cmd/nodepool/azure/create_test.go|TestValidateImageGeneration": {}, "cmd/nodepool/gcp/create_test.go|TestCLIFlow": {}, "cmd/nodepool/kubevirt/create_test.go|TestCreateNodePool_When_flags_are_parsed_it_should_generate_correct_nodepool": {}, @@ -98,6 +120,7 @@ var legacyExceptions = sets.Set[string]{ "cmd/oadp/backup_test.go|TestGenerateBackupObjectWithCustomName": {}, "cmd/oadp/backup_test.go|TestGenerateBackupObjectWithIncludedNamespaces": {}, "cmd/oadp/backup_test.go|TestGenerateBackupObjectWithKubeVirtResourcePolicy": {}, + "cmd/oadp/backup_test.go|TestRunBackupClientProvider": {}, "cmd/oadp/common_test.go|TestApplyPlatformBackupSpec": {}, "cmd/oadp/common_test.go|TestApplyPlatformBackupSpecPreservesExistingFields": {}, "cmd/oadp/restore_test.go|TestBuildIncludedNamespaces": {}, @@ -111,29 +134,16 @@ var legacyExceptions = sets.Set[string]{ "cmd/oadp/schedule_test.go|TestGenerateScheduleObjectComprehensive": {}, "cmd/oadp/schedule_test.go|TestGenerateScheduleObjectWithIncludedNamespaces": {}, "cmd/oadp/schedule_test.go|TestGenerateScheduleObjectWithKubeVirtResourcePolicy": {}, + "cmd/oadp/schedule_test.go|TestRunSchedule": {}, + "cmd/oadp/schedule_test.go|TestRunScheduleClientProvider": {}, "cmd/oadp/schedule_test.go|TestScheduleCommandIncludedNamespacesFlag": {}, "cmd/oadp/schedule_test.go|TestScheduleNameValidation": {}, - "contrib/aws-leaked-resources/delete_test.go|TestDeleter_ActiveInfraSetNeverDeleted": {}, - "contrib/aws-leaked-resources/delete_test.go|TestDeleter_CollectNATGatewayEIPsReturnsCorrectIDs": {}, - "contrib/aws-leaked-resources/delete_test.go|TestDeleter_DefaultSGNeverDeleted": {}, - "contrib/aws-leaked-resources/delete_test.go|TestDeleter_EIPRelease": {}, - "contrib/aws-leaked-resources/delete_test.go|TestDeleter_ELBOnlyDeletesTargetVPC": {}, - "contrib/aws-leaked-resources/delete_test.go|TestDeleter_FullCascadeOnlyTargetsOwnResources": {}, - "contrib/aws-leaked-resources/delete_test.go|TestDeleter_MainRTBNeverDeleted": {}, - "contrib/aws-leaked-resources/delete_test.go|TestDeleter_ProtectedInfraSetNeverDeleted": {}, - "contrib/aws-leaked-resources/delete_test.go|TestDeleter_RealWorldScenario_CI2EIPSurvives": {}, - "contrib/aws-leaked-resources/delete_test.go|TestDeleter_VPCEndpointServicesOnlyDeletesMatchingInfraID": {}, - "contrib/aws-leaked-resources/scan_test.go|TestCheckEC2Instances_APIError": {}, - "contrib/aws-leaked-resources/scan_test.go|TestFullScan_MixedVPCs": {}, - "contrib/aws-leaked-resources/scan_test.go|TestGate0_DoNotDeleteTag": {}, - "contrib/aws-leaked-resources/scan_test.go|TestGate0_ProtectedUsers": {}, - "contrib/aws-leaked-resources/scan_test.go|TestGate0_ProtectedVPCs": {}, - "contrib/aws-leaked-resources/scan_test.go|TestGate1_Age": {}, - "contrib/aws-leaked-resources/scan_test.go|TestGate1_AgeUnknown": {}, - "contrib/aws-leaked-resources/scan_test.go|TestGate1_ExpirationDate": {}, - "contrib/aws-leaked-resources/scan_test.go|TestGate3_OIDC": {}, - "contrib/aws-leaked-resources/scan_test.go|TestGate4_EC2Instances": {}, - "contrib/aws-leaked-resources/scan_test.go|TestUncertainVerdict": {}, + "cmd/util/client_test.go|TestClientProviderErrors": {}, + "cmd/util/client_test.go|TestConfigFor": {}, + "cmd/util/client_test.go|TestControllerRuntimeClientFor": {}, + "cmd/util/client_test.go|TestImpersonatedClientFor": {}, + "cmd/util/client_test.go|TestKubernetesClientSetFor": {}, + "control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller_test.go|TestDeleteLocalZoneCleanup": {}, "control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller_test.go|TestHCPExternalNames": {}, "control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller_test.go|TestReconcileDeletion": {}, "control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller_test.go|TestReconcileDeletionSharedVPC": {}, @@ -249,10 +259,17 @@ var legacyExceptions = sets.Set[string]{ "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestDNSEndpointNameTrimming": {}, "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestDNSEndpointNaming": {}, "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestEndpointNameUniqueness": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestEnqueueOnResourceLabelsChange": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestEnsureIPAddress": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestEnsureIPAddressContinuesWhenLabelReconciliationFails": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestEnsureIPAddressCreatesAddressWithLabels": {}, "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestHCPExternalNamesGCP": {}, "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestIPAddressNameUniqueness": {}, "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestNameserverTrailingDotTrimming": {}, "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestNamingFunctionConsistency": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestReconcilePSCEndpointContinuesWhenLabelReconciliationFails": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestReconcilePSCEndpointCreatesAndContinuesWhenLabelReconciliationFails": {}, + "control-plane-operator/controllers/gcpprivateserviceconnect/psc_endpoint_controller_test.go|TestReconcilePSCEndpointCreatesThenLabelsForwardingRule": {}, "control-plane-operator/controllers/healthcheck/aws_test.go|TestAWSHealthCheckIdentityProviderEC2ClientNil": {}, "control-plane-operator/controllers/healthcheck/aws_test.go|TestAWSHealthCheckIdentityProviderWithMockEC2": {}, "control-plane-operator/controllers/healthcheck/aws_test.go|TestClassifyIdentityProviderError": {}, @@ -261,6 +278,10 @@ var legacyExceptions = sets.Set[string]{ "control-plane-operator/controllers/healthcheck/gcp_test.go|TestGCPHealthCheckConditionDifferentiation": {}, "control-plane-operator/controllers/healthcheck/gcp_test.go|TestGCPHealthCheckIdentityProviderConditionLogic": {}, "control-plane-operator/controllers/healthcheck/gcp_test.go|TestUpdateRunsGCPIdentityCheckDuringDeletion": {}, + "control-plane-operator/controllers/hostedcontrolplane/aws_ingress_dns_test.go|TestReconcileIngressDNSSharedVPC": {}, + "control-plane-operator/controllers/hostedcontrolplane/aws_ingress_dns_test.go|TestReconcileNSDelegationNoDelegation": {}, + "control-plane-operator/controllers/hostedcontrolplane/aws_ingress_dns_test.go|TestReconcilePrivateZone": {}, + "control-plane-operator/controllers/hostedcontrolplane/aws_ingress_dns_test.go|TestReconcilePublicZone": {}, "control-plane-operator/controllers/hostedcontrolplane/controlplaneversion_pruning_test.go|TestPruneHistory": {}, "control-plane-operator/controllers/hostedcontrolplane/controlplaneversion_pruning_test.go|TestPruneHistory_CapAtMaxHistory": {}, "control-plane-operator/controllers/hostedcontrolplane/controlplaneversion_pruning_test.go|TestPruneHistory_DeterministicResults": {}, @@ -279,11 +300,17 @@ var legacyExceptions = sets.Set[string]{ "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestAWSSecurityGroupTags": {}, "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestControlPlaneComponents": {}, "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestEventHandling": {}, + "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestGCPFirewallSkipOnWIFUnavailableReason": {}, "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestHealthCheckKASEndpoint": {}, "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestHealthCheckKASEndpointMetrics": {}, + "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestKASHealthCheckURL": {}, + "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestMarkGCPFirewallProvisioned": {}, "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestNonReadyInfraTriggersRequeueAfter": {}, "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestReconcileDefaultSecurityGroup": {}, "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestReconcileDefaultSecurityGroup_GenerationConflict": {}, + "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestReconcileDeletion": {}, + "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestReconcileDeletionAWSEndpointServices": {}, + "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestReconcileFailsWhenCombinedPullSecretCreateFails": {}, "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestReconcileIgnitionServer": {}, "control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go|TestRouterComponentComesAfterRouteCreatingComponents": {}, "control-plane-operator/controllers/hostedcontrolplane/infra/infra_test.go|TestReconcileInfrastructure": {}, @@ -312,6 +339,22 @@ var legacyExceptions = sets.Set[string]{ "control-plane-operator/controllers/hostedcontrolplane/oauth/service_test.go|TestOauthServiceReconcile": {}, "control-plane-operator/controllers/hostedcontrolplane/pki/azure_workload_identity_webhook_test.go|TestReconcileAzureWorkloadIdentityWebhookServingCert": {}, "control-plane-operator/controllers/hostedcontrolplane/pki/azure_workload_identity_webhook_test.go|TestReconcileAzureWorkloadIdentityWebhookServingCertIdempotent": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/ca_test.go|TestReconcileTotalClientCA_DoesNotRetainRemovedAdditionalCAs": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/ca_test.go|TestReconcileTotalClientCA_EmptyAdditionalSource": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/ca_test.go|TestReconcileTotalClientCA_EmptyPreviousBundle": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/ca_test.go|TestReconcileTotalClientCA_EvictsExpiredCAs": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/ca_test.go|TestReconcileTotalClientCA_LegacyAggregateFailsClosedAfterAdditionalRemoval": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/ca_test.go|TestReconcileTotalClientCA_LegacyAggregateRetainsUnambiguousDirectCA": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/ca_test.go|TestReconcileTotalClientCA_NoDuplicatesOnSteadyState": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/ca_test.go|TestReconcileTotalClientCA_PreservesPreviousCAsOnRotation": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/ca_test.go|TestReconcileTotalClientCA_ProvenanceDisambiguatesSameSubjectSources": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/ca_test.go|TestReconcileTotalClientCA_ProvenanceMetadata": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/ca_test.go|TestReconcileTotalClientCA_RefusesEmptyBundle": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/ca_test.go|TestReconcileTotalClientCA_SequentialRotation": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/ca_test.go|TestReconcileTotalClientCA_SkipsMalformedPreviousCertificate": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/ca_test.go|TestReconcileTotalClientCA_WithAdditionalConfigMaps": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/gcp_workload_identity_federation_webhook_test.go|TestReconcileGCPWorkloadIdentityFederationWebhookServingCert": {}, + "control-plane-operator/controllers/hostedcontrolplane/pki/gcp_workload_identity_federation_webhook_test.go|TestReconcileGCPWorkloadIdentityFederationWebhookServingCertIdempotent": {}, "control-plane-operator/controllers/hostedcontrolplane/pki/konnectivity_test.go|TestKonnectivitySeparateSigners": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/assets/assets_test.go|TestForEachManifest": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/assets/assets_test.go|TestForEachManifestWithActionError": {}, @@ -354,6 +397,8 @@ var legacyExceptions = sets.Set[string]{ "control-plane-operator/controllers/hostedcontrolplane/v2/controlplaneoperator/deployment_test.go|TestAdaptDeploymentWatchListClientEnv": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/csi/kubevirt/config_test.go|TestAdaptConfigMapConsistentOrdering": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/csi/kubevirt/config_test.go|TestAdaptConfigMapEmptyMappings": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cvo/deployment_test.go|TestAdaptDeployment": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/cvo/deployment_test.go|TestAdaptDeploymentClusterVersionCapabilities": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/dnsoperator/component_test.go|TestComponentOptions": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/etcd/etcd_test.go|TestAdaptStatefulSet": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/etcd/etcd_test.go|TestAdaptStatefulSetEtcdLogLevel": {}, @@ -374,8 +419,13 @@ var legacyExceptions = sets.Set[string]{ "control-plane-operator/controllers/hostedcontrolplane/v2/ignitionserver_proxy/service_test.go|TestAdaptServiceValidatesStrategyType": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/ignitionserver_proxy/service_test.go|TestServicePublishingStrategyByTypeForHCP": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/karpenteroperator/component_test.go|TestKarpenterCredentialsSecretReconcile": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/karpenteroperator/deployment_test.go|TestAdaptStandaloneDeployment": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/karpenteroperator/deployment_test.go|TestAdaptStandaloneDeploymentEndpoint": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/deployment_test.go|TestAdaptDeployment": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/kas/deployment_test.go|TestAdaptDeploymentKASLogLevel": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/kas/deployment_test.go|TestKonnectivityServerTLSMinVersion": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/gcp_workload_identity_federation_webhook_test.go|TestApplyGCPWorkloadIdentityFederationWebhookContainer": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/kas/gcp_workload_identity_federation_webhook_test.go|TestApplyGCPWorkloadIdentityFederationWebhookContainerError": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go|TestGenerateKMSEncryptionConfig_Azure": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go|TestGenerateKMSPodConfig_ActiveContainerArgs": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go|TestGenerateKMSPodConfig_BackupContainerArgs": {}, @@ -423,8 +473,7 @@ var legacyExceptions = sets.Set[string]{ "control-plane-operator/controllers/hostedcontrolplane/v2/nto/servicemonitor_test.go|TestAdaptServiceMonitor": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/nto/servicemonitor_test.go|TestAdaptServiceMonitorErrorCases": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/nto/servicemonitor_test.go|TestAdaptServiceMonitorMetricsSetConfigurations": {}, - "control-plane-operator/controllers/hostedcontrolplane/v2/oapi/deployment_test.go|TestAdaptCombinedPullSecret": {}, - "control-plane-operator/controllers/hostedcontrolplane/v2/oapi/deployment_test.go|TestAdaptCombinedPullSecretMissingPullSecret": {}, + "control-plane-operator/controllers/hostedcontrolplane/v2/oapi/deployment_test.go|TestAdaptDeployment": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/oapi/deployment_test.go|TestAdaptDeploymentOAPILogLevel": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/oapi/deployment_test.go|TestReconcileOpenshiftAPIServerDeploymentTrustBundle": {}, "control-plane-operator/controllers/hostedcontrolplane/v2/oauth/deployment_test.go|TestAdaptDeployment": {}, @@ -469,26 +518,35 @@ var legacyExceptions = sets.Set[string]{ "control-plane-operator/hostedclusterconfigoperator/controllers/reencryption/reencryption_test.go|TestReconcile_PatchStatusError": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/azure_workload_identity_webhook_test.go|TestReconcileAzureIdentityWebhook": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/azure_workload_identity_webhook_test.go|TestReconcileAzureIdentityWebhookIdempotent": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/configuration/cluster_version_test.go|TestReconcileClusterVersion": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/configuration/cluster_version_test.go|TestReconcileClusterVersionWhenGuestCVOHasOlderCapabilities": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/configuration/cluster_version_test.go|TestReconcileClusterVersionWithDisabledCapabilities": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/configuration/cluster_version_test.go|TestReconcileClusterVersionWithEnabledCapabilities": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/foundation_order_test.go|TestReconcile": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/gcp/gcp_test.go|TestReconcileGCPCredentials_EmptySecretKey": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/gcp/gcp_test.go|TestSetupOperandCredentials": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/gcp/gcp_test.go|TestSetupOperandCredentials_Storage": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/gcp_lb_service_annotations_test.go|TestReconcileGCPLoadBalancerServiceAnnotations": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/gcp_lb_service_annotations_test.go|TestReconcileGCPLoadBalancerServiceAnnotationsErrors": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/gcp_lb_service_annotations_test.go|TestReconcileGCPLoadBalancerServiceAnnotationsOptimisticLock": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/gcp_workload_identity_federation_webhook_test.go|TestReconcileGCPIdentityWebhook": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/gcp_workload_identity_federation_webhook_test.go|TestReconcileGCPIdentityWebhookIdempotent": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/ingress/reconcile_test.go|TestConfigurationPriority": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/ingress/reconcile_test.go|TestReconcileDefaultIngressController": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/ingress/reconcile_test.go|TestReconcileDefaultIngressControllerWithCustomEndpointPublishingStrategy": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/monitoring/metricsforwarder_test.go|TestReconcileMetricsForwarderDeployment": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/monitoring/metricsforwarder_test.go|TestReconcileMetricsForwarderDeployment_ConfigHashChangeTrigersRollout": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/network/reconcile_test.go|TestReconcileDefaultIngressController": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/oapi/encryption_status_test.go|TestReconcileEncryptionCondition": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/oapi/encryption_status_test.go|TestReconcileEncryptionConditionIdempotent": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/olm/catalogs_test.go|TestReconcileCatalogSourcePreservesExistingAnnotations": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestConnectionErrorTracking": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestDestroyCloudResources": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestDestroyCloudResourcesWithKASUnavailable": {}, - "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestReconcileClusterVersion": {}, - "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestReconcileClusterVersionWhenGuestCVOHasOlderCapabilities": {}, - "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestReconcileClusterVersionWithDisabledCapabilities": {}, - "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestReconcileClusterVersionWithEnabledCapabilities": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestReconcileErrorHandling": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestReconcileIngressControllerCertSource": {}, "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestReconcileRegistryAndIngress_ServiceAccountPullSecretsController": {}, + "control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go|TestReconcileRequeueBehavior": {}, "control-plane-operator/metrics-proxy/filter_test.go|TestFilter_Apply": {}, "control-plane-operator/metrics-proxy/filter_test.go|TestFilter_Apply_ConcurrentAccess": {}, "control-plane-operator/metrics-proxy/filter_test.go|TestFilter_getOrCompile": {}, @@ -508,6 +566,27 @@ var legacyExceptions = sets.Set[string]{ "etcd-upload/azure_uploader_test.go|TestAzureBlobUploader": {}, "etcd-upload/azure_uploader_test.go|TestAzureCredential": {}, "etcd-upload/s3_uploader_test.go|TestS3Uploader": {}, + "hack/copy-capi-types/main_test.go|TestCrossFileCascade_FieldNameCollision": {}, + "hack/copy-capi-types/main_test.go|TestCrossFileCascade_FullFileSkip": {}, + "hack/copy-capi-types/main_test.go|TestCrossFileCascade_FuncDecl": {}, + "hack/copy-capi-types/main_test.go|TestCrossFileCascade_GenDecl": {}, + "hack/copy-capi-types/main_test.go|TestCrossFileCascade_NoStrippedSymbols": {}, + "hack/copy-capi-types/main_test.go|TestCrossFileCascade_TransitiveCascade": {}, + "hack/copy-capi-types/main_test.go|TestCrossFileCascade_UpdatesAllStrippedSymbols": {}, + "hack/copy-capi-types/main_test.go|TestNodeReferencesAny_GenDecl": {}, + "hack/copy-capi-types/main_test.go|TestProcessFile_BannedVarDecl": {}, + "hack/copy-capi-types/main_test.go|TestProcessFile_DotImport": {}, + "hack/copy-capi-types/main_test.go|TestProcessFile_FullFileSkip": {}, + "hack/copy-capi-types/main_test.go|TestProcessFile_ImportCleanup": {}, + "hack/copy-capi-types/main_test.go|TestProcessFile_MethodReceiver": {}, + "hack/copy-capi-types/main_test.go|TestProcessFile_MultiSpecVarBlock": {}, + "hack/copy-capi-types/main_test.go|TestProcessFile_NoBannedImports": {}, + "hack/copy-capi-types/main_test.go|TestProcessFile_SkipByName": {}, + "hack/copy-capi-types/main_test.go|TestProcessFile_SkipBySuffix": {}, + "hack/copy-capi-types/main_test.go|TestProcessFile_StripBannedFunctions": {}, + "hack/copy-capi-types/main_test.go|TestProcessFile_StripBannedTypes": {}, + "hack/copy-capi-types/main_test.go|TestProcessFile_WithinFileCascade": {}, + "hack/copy-capi-types/main_test.go|TestReferencesAny": {}, "hypershift-operator/controllers/auditlogpersistence/snapshot_controller_test.go|TestSnapshotReconciler_createSnapshot": {}, "hypershift-operator/controllers/auditlogpersistence/snapshot_controller_test.go|TestSnapshotReconciler_manageRetention": {}, "hypershift-operator/controllers/etcdbackup/credentials_test.go|TestCredentialBehavioralMethods": {}, @@ -528,6 +607,7 @@ var legacyExceptions = sets.Set[string]{ "hypershift-operator/controllers/etcdbackup/reconciler_test.go|TestReconcileRecoversWhenCredentialSecretAppears": {}, "hypershift-operator/controllers/etcdbackup/reconciler_test.go|TestReconcileTerminalCleansUpAndEnforcesRetention": {}, "hypershift-operator/controllers/hostedcluster/enqueue_secret_test.go|TestEnqueueHostedClustersForReferencedSecret": {}, + "hypershift-operator/controllers/hostedcluster/gcp_firewall_condition_test.go|TestGCPFirewallRulesReadyCondition": {}, "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestDeleteOrcImagesDuringHostedClusterDeletion": {}, "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestDestroyGracePeriod": {}, "hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go|TestHasBeenAvailable": {}, @@ -558,6 +638,8 @@ var legacyExceptions = sets.Set[string]{ "hypershift-operator/controllers/hostedcluster/hostedcluster_webhook_test.go|TestValidateKVNodePoolUpdate": {}, "hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure_test.go|TestDeleteOrphanedMachines": {}, "hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure_test.go|TestDeleteOrphanedMachines_CapiProviderLookupError": {}, + "hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure_test.go|TestReconcileCredentials": {}, + "hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure_test.go|TestReconcileCredentialsPreservesWrappedErrors": {}, "hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_conditions_test.go|TestNetworkConfigAccessSafety": {}, "hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_conditions_test.go|TestWorkloadIdentityValidationScenarios": {}, "hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_test.go|TestCAPIProviderDeploymentSpec": {}, @@ -567,6 +649,7 @@ var legacyExceptions = sets.Set[string]{ "hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_test.go|TestReconcileCredentials": {}, "hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_test.go|TestReconcileCredentialsNilGCPPlatform": {}, "hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_test.go|TestReconcileGCPClusterPreservesServerDefaultedFields": {}, + "hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_test.go|TestReconcileGCPClusterResourceLabels": {}, "hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_test.go|TestValidateWorkloadIdentityConfiguration": {}, "hypershift-operator/controllers/hostedcluster/internal/platform/openstack/openstack_test.go|TestCAPIProviderDeploymentSpec": {}, "hypershift-operator/controllers/hostedcluster/internal/platform/openstack/openstack_test.go|TestCAPIProviderDeploymentSpecWithTLS": {}, @@ -607,6 +690,9 @@ var legacyExceptions = sets.Set[string]{ "hypershift-operator/controllers/nodepool/aws_test.go|TestAWSMachineTemplateSpec_StreamSelection": {}, "hypershift-operator/controllers/nodepool/aws_test.go|TestAWSTagConflicts": {}, "hypershift-operator/controllers/nodepool/aws_test.go|TestApplyAWSMachineOptions": {}, + "hypershift-operator/controllers/nodepool/azure_test.go|TestAzureMachineTemplateSpec": {}, + "hypershift-operator/controllers/nodepool/azure_test.go|TestAzureMachineTemplateSpecIPForwardingDefaultsOff": {}, + "hypershift-operator/controllers/nodepool/azure_test.go|TestAzureMachineTemplateSpecIPForwardingSerialization": {}, "hypershift-operator/controllers/nodepool/capi_test.go|TestCAPIReconcile": {}, "hypershift-operator/controllers/nodepool/capi_test.go|TestCAPIReconcile_machineset": {}, "hypershift-operator/controllers/nodepool/capi_test.go|TestGlobalPSManagedLabelOnMachines": {}, @@ -624,9 +710,14 @@ var legacyExceptions = sets.Set[string]{ "hypershift-operator/controllers/nodepool/capi_test.go|TestReconcileSelectorDropsStaleLabels": {}, "hypershift-operator/controllers/nodepool/conditions_test.go|TestGenerateReconciliationPausedCondition": {}, "hypershift-operator/controllers/nodepool/config_test.go|TestCloudConfigHash": {}, + "hypershift-operator/controllers/nodepool/config_test.go|TestGlobalConfigString": {}, "hypershift-operator/controllers/nodepool/config_test.go|TestMissingCoreConfigError": {}, + "hypershift-operator/controllers/nodepool/config_test.go|TestRolloutGlobalConfigString": {}, + "hypershift-operator/controllers/nodepool/config_test.go|TestRolloutHash": {}, + "hypershift-operator/controllers/nodepool/config_test.go|TestRolloutHashAnnotationSeeding": {}, "hypershift-operator/controllers/nodepool/gcp_test.go|TestGcpMachineTemplateSpec": {}, "hypershift-operator/controllers/nodepool/gcp_test.go|TestGcpMachineTemplateSpecWithRHELStream": {}, + "hypershift-operator/controllers/nodepool/gcp_test.go|TestNodePoolReconciler_setGCPConditions": {}, "hypershift-operator/controllers/nodepool/instancetype/aws/provider_test.go|TestTransformInstanceTypeInfoWhenMissingRequiredFieldsItShouldReturnError": {}, "hypershift-operator/controllers/nodepool/instancetype/aws/provider_test.go|TestTransformInstanceTypeInfoWhenValidInputItShouldTransformCorrectly": {}, "hypershift-operator/controllers/nodepool/instancetype/azure/provider_test.go|TestCacheTTLRefresh": {}, @@ -645,12 +736,14 @@ var legacyExceptions = sets.Set[string]{ "hypershift-operator/controllers/nodepool/nodepool_controller_test.go|TestNodePoolReconciler_reconcile": {}, "hypershift-operator/controllers/nodepool/nodepool_controller_test.go|TestNodepoolDeletionDoesntRequireHCluster": {}, "hypershift-operator/controllers/nodepool/nodepool_controller_test.go|TestSetCIDRConflictConditionDualStack": {}, - "hypershift-operator/controllers/nodepool/nto_test.go|TestSetPerformanceProfileStatus": {}, "hypershift-operator/controllers/nodepool/openstack/openstack_test.go|TestClusterImageName": {}, "hypershift-operator/controllers/nodepool/openstack/openstack_test.go|TestOpenStackMachineTemplate": {}, + "hypershift-operator/controllers/nodepool/reconcile_test.go|TestReconcile": {}, "hypershift-operator/controllers/nodepool/secret_janitor_test.go|TestSecretJanitor_Reconcile": {}, "hypershift-operator/controllers/nodepool/token_test.go|TestOutdatedUserdataSecret": {}, "hypershift-operator/controllers/nodepool/token_test.go|TestTokenReconcile": {}, + "hypershift-operator/controllers/nodepool/token_test.go|TestTokenReconcileAfterTokenRotation": {}, + "hypershift-operator/controllers/nodepool/token_test.go|TestTokenReconcileManagementSideDrift": {}, "hypershift-operator/controllers/nodepool/token_test.go|TestTokenUserDataSecret": {}, "hypershift-operator/controllers/nodepool/tracing_test.go|TestNodePoolReconcileTracingDeletingNodePool": {}, "hypershift-operator/controllers/nodepool/tracing_test.go|TestNodePoolReconcileTracingNotFoundSkipsSpan": {}, @@ -658,6 +751,8 @@ var legacyExceptions = sets.Set[string]{ "hypershift-operator/controllers/nodepool/tracing_test.go|TestNodePoolReconcileTracingSpanAttributes": {}, "hypershift-operator/controllers/nodepool/tracing_test.go|TestNodePoolReconcileTracingSpanLinkFromTraceparent": {}, "hypershift-operator/controllers/platform/aws/controller_test.go|TestDeleteAWSEndpointService": {}, + "hypershift-operator/controllers/platform/aws/controller_test.go|TestReconcileAWSEndpointServiceStatus": {}, + "hypershift-operator/controllers/platform/aws/controller_test.go|TestReconcileAWSEndpointServiceStatusPreservesAWSAPIError": {}, "hypershift-operator/controllers/platform/aws/controller_test.go|TestReconcileRequeuesWithJitter": {}, "hypershift-operator/controllers/platform/azure/controller_test.go|TestDelete_WhenNoPEConnections_ItShouldProceedToDelete": {}, "hypershift-operator/controllers/platform/azure/controller_test.go|TestDelete_WhenPEConnectionsExist_ItShouldRejectAndRequeue": {}, @@ -671,6 +766,9 @@ var legacyExceptions = sets.Set[string]{ "hypershift-operator/controllers/platform/azure/controller_test.go|TestReconcile_WhenLoadBalancerIPIsSet_ItShouldCreateAPLS": {}, "hypershift-operator/controllers/platform/azure/controller_test.go|TestReconcile_WhenLoadBalancerIPNotSet_ItShouldRequeue": {}, "hypershift-operator/controllers/platform/azure/controller_test.go|TestReconcile_WhenPLSAlreadyExists_ItShouldUpdateStatus": {}, + "hypershift-operator/controllers/platform/gcp/loadbalancer_labels_controller_test.go|TestGCPLoadBalancerLabelsReconciler": {}, + "hypershift-operator/controllers/platform/gcp/loadbalancer_labels_controller_test.go|TestGCPLoadBalancerLabelsReconcilerRecordsManagedLabelsAfterCompletedOperation": {}, + "hypershift-operator/controllers/platform/gcp/loadbalancer_labels_controller_test.go|TestGCPLoadBalancerLabelsReconcilerSetsPendingCondition": {}, "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestComputeServiceAdapterListServiceAttachmentsPagination": {}, "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestComputeServiceAdapterListSubnetworksPagination": {}, "hypershift-operator/controllers/platform/gcp/privateserviceconnect_controller_test.go|TestDiscoverNATSubnetAPIError": {}, @@ -736,7 +834,6 @@ var legacyExceptions = sets.Set[string]{ "karpenter-operator/controllers/karpenterignition/karpenterignition_controller_test.go|TestReconcileKubeletConfigMapOrphanCleanup": {}, "karpenter-operator/controllers/karpenterignition/karpenterignition_controller_test.go|TestReconcileVersionResolution": {}, "karpenter-operator/controllers/karpenterignition/karpenterignition_controller_test.go|TestResolveVersion": {}, - "karpenter-operator/controllers/nodeclass/ec2_nodeclass_controller_test.go|TestReconcileCRDsStandaloneAdapterOnlyInstallsHyperShiftCRD": {}, "karpenter-operator/controllers/nodeclass/ec2_nodeclass_controller_test.go|TestReconcileEC2NodeClass": {}, "karpenter-operator/controllers/nodeclass/ec2_nodeclass_controller_test.go|TestReconcileEC2NodeClassUpgradePause": {}, "karpenter-operator/controllers/nodeclass/ec2_nodeclass_controller_test.go|TestReconcileStatus": {}, @@ -744,6 +841,8 @@ var legacyExceptions = sets.Set[string]{ "karpenter-operator/controllers/nodeclass/ec2_nodeclass_controller_test.go|TestReconcileStatusTagConflictCondition": {}, "pkg/featuregates/featuregates_test.go|TestConfiguringUnknownFeatureSetErrors": {}, "pkg/featuregates/featuregates_test.go|TestCreatingFeatureGates": {}, + "product-cli/cmd/cluster/aws/create_test.go|TestNewCreateCommand": {}, + "product-cli/cmd/cluster/aws/create_test.go|TestNewCreateCommandClientProvider": {}, "product-cli/cmd/kubeconfig/create_test.go|TestNewCreateCommand": {}, "product-cli/cmd/kubeconfig/create_test.go|TestNewCreateCommandRunE": {}, "product-cli/cmd/nodepool/agent/create_test.go|TestNewCreateCommand": {}, @@ -763,6 +862,8 @@ var legacyExceptions = sets.Set[string]{ "support/api/scheme_test.go|TestEncode_NonMetaObject": {}, "support/awsutil/sg_test.go|TestUpdateResourceTags_FiltersAWSReservedKeys": {}, "support/azureutil/azureutil_test.go|TestGetNetworkSecurityGroupNameFromNetworkSecurityGroupID": {}, + "support/capabilities/hosted_control_plane_capabilities_test.go|TestCalculateEnabledCapabilities": {}, + "support/capabilities/hosted_control_plane_capabilities_test.go|TestCalculateEnabledCapabilitiesIncludesCompatibilityRequirements": {}, "support/capabilities/management_cluster_capabilities_test.go|TestDetectManagementCapabilities": {}, "support/capabilities/management_cluster_capabilities_test.go|TestDetectNativeSidecarCapability": {}, "support/catalogs/images_test.go|TestImageExistsFnGuestCluster": {}, @@ -783,9 +884,18 @@ var legacyExceptions = sets.Set[string]{ "support/controlplane-component/konnectivity-container_test.go|TestBuildContainer": {}, "support/controlplane-component/konnectivity-container_test.go|TestBuildContainerDualMode": {}, "support/controlplane-component/konnectivity-container_test.go|TestBuildContainerPreferIPv4": {}, + "support/controlplane-component/precondition_test.go|TestReconcileFailsWhenGateErrors": {}, + "support/controlplane-component/precondition_test.go|TestReconcileSkipsWorkloadWhenPreconditionUnmet": {}, "support/gcputil/gcputil_test.go|TestBuildWorkloadIdentityCredentials": {}, "support/gcputil/gcputil_test.go|TestBuildWorkloadIdentityCredentialsValidation": {}, + "support/gcputil/lb_resource_labels_test.go|TestReconcileLBResourceLabelAnnotations": {}, + "support/gcputil/lb_resource_labels_test.go|TestReconcileLBResourceLabelAnnotationsFirstTimeCollisionAdoption": {}, "support/globalconfig/imagecontentsource_test.go|TestReconcileMgmtImageRegistryOverrides": {}, + "support/imageregistry/imagemetadata_test.go|TestCacheCleanupOnExpiration": {}, + "support/imageregistry/imagemetadata_test.go|TestMirrorAvailabilityCache": {}, + "support/imageregistry/imagemetadata_test.go|TestSeekOverride": {}, + "support/imageregistry/imagemetadata_test.go|TestSeekOverrideFallsBackWhenMirrorUnavailable": {}, + "support/imageregistry/imagemetadata_test.go|TestSeekOverrideWithCache": {}, "support/k8sutil/serializer_test.go|TestDeserializeResource": {}, "support/k8sutil/serializer_test.go|TestSerializeDeserializeRoundTrip": {}, "support/k8sutil/serializer_test.go|TestSerializeResource": {}, @@ -854,11 +964,6 @@ var legacyExceptions = sets.Set[string]{ "support/upsert/apply_test.go|TestApplyManifestLabelRemovalWithEmptyLabels": {}, "support/upsert/apply_test.go|TestApplyManifest_DesiredStateHash": {}, "support/upsert/apply_test.go|TestApplyManifest_DesiredStateHashEdgeCases": {}, - "support/util/imagemetadata_test.go|TestCacheCleanupOnExpiration": {}, - "support/util/imagemetadata_test.go|TestMirrorAvailabilityCache": {}, - "support/util/imagemetadata_test.go|TestSeekOverride": {}, - "support/util/imagemetadata_test.go|TestSeekOverrideFallsBackWhenMirrorUnavailable": {}, - "support/util/imagemetadata_test.go|TestSeekOverrideWithCache": {}, "support/util/loglevel_test.go|TestLogLevelToEtcdLevel": {}, "support/util/loglevel_test.go|TestLogLevelToEtcdLevelPanicsOnUnsupported": {}, "support/util/registryoverride/registryoverride_test.go|TestReplace": {}, diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/identity/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/identity/production.go new file mode 100644 index 000000000000..765dae8c8a9c --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/identity/production.go @@ -0,0 +1,7 @@ +package identity // want package:"testfuncstructure production symbols" + +func Run() {} + +type Box[T any] struct{} + +func (Box[T]) Execute() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/identity/production_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/identity/production_test.go new file mode 100644 index 000000000000..f8396c5eec8c --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/identity/production_test.go @@ -0,0 +1,12 @@ +package identity + +import "testing" + +func TestAnonymousInterfaceCall(t *testing.T) { + var runner interface{ Run() } + runner.Run() +} + +func TestGenericMethodCall(t *testing.T) { // want `test function "TestGenericMethodCall" must be named "TestBox_Execute" to map to Box.Execute` + Box[int]{}.Execute() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/reversecross/external_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/reversecross/external_test.go new file mode 100644 index 000000000000..b8859ecfb3a2 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/reversecross/external_test.go @@ -0,0 +1,11 @@ +package reversecross_test + +import ( + "testing" + + "a/reversecross" +) + +func TestValidateErrors(t *testing.T) { + reversecross.Validate() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/reversecross/internal_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/reversecross/internal_test.go new file mode 100644 index 000000000000..e352f3abc615 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/reversecross/internal_test.go @@ -0,0 +1,7 @@ +package reversecross + +import "testing" + +func TestValidate(t *testing.T) { + Validate() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/reversecross/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/reversecross/production.go new file mode 100644 index 000000000000..bcdf640f073a --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/reversecross/production.go @@ -0,0 +1,3 @@ +package reversecross + +func Validate() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go index ca2b04055931..966c713d06c1 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go @@ -28,6 +28,7 @@ var Analyzer = &analysis.Analyzer{ type productionSymbol struct { id string + fn *types.Func display string canonicalTest string variants []testNameVariant @@ -78,14 +79,16 @@ var excludedBuildTags = sets.New("e2e", "e2ev2", "envtest", "integration", "reqs func run(pass *analysis.Pass) (any, error) { symbols := collectProductionSymbols(pass) symbolsByID := make(map[string]*productionSymbol, len(symbols)) + symbolsByFunc := make(map[*types.Func]*productionSymbol, len(symbols)) for _, symbol := range symbols { symbolsByID[symbol.id] = symbol + symbolsByFunc[symbol.fn] = symbol } tests := collectTests(pass) groups := map[string][]*testFunction{} for _, test := range tests { - test.resolution = resolveTest(pass, test, symbols, symbolsByID) + test.resolution = resolveTest(pass, test, symbols, symbolsByFunc) switch test.resolution.kind { case resolutionExact, resolutionScenario, resolutionDisconnected: groups[test.resolution.target.id] = append(groups[test.resolution.target.id], test) @@ -229,12 +232,20 @@ func addProductionSymbol(seen map[string]*productionSymbol, fn *types.Func) { id := symbolID(fn) seen[id] = &productionSymbol{ id: id, + fn: functionOrigin(fn), display: display, canonicalTest: canonical, variants: variants, } } +func functionOrigin(fn *types.Func) *types.Func { + if fn == nil { + return nil + } + return fn.Origin() +} + func receiverName(fn *types.Func) string { signature, ok := fn.Type().(*types.Signature) if !ok || signature.Recv() == nil { @@ -436,8 +447,8 @@ func hasNoLintDirective(fn *ast.FuncDecl) bool { return false } -func resolveTest(pass *analysis.Pass, test *testFunction, symbols []*productionSymbol, symbolsByID map[string]*productionSymbol) resolution { - called := calledProductionSymbols(pass, test.decl, symbolsByID) +func resolveTest(pass *analysis.Pass, test *testFunction, symbols []*productionSymbol, symbolsByFunc map[*types.Func]*productionSymbol) resolution { + called := calledProductionSymbols(pass, test.decl, symbolsByFunc) var exactMatches, scenarioMatches []symbolMatch for _, symbol := range symbols { match, ok := matchSymbol(test.name, symbol) @@ -466,8 +477,8 @@ func resolveTest(pass *analysis.Pass, test *testFunction, symbols []*productionS } calledSymbols := make([]*productionSymbol, 0, len(called)) - for id := range called { - calledSymbols = append(calledSymbols, symbolsByID[id]) + for symbol := range called { + calledSymbols = append(calledSymbols, symbol) } slices.SortFunc(calledSymbols, func(a, b *productionSymbol) int { return strings.Compare(a.id, b.id) @@ -538,14 +549,14 @@ func betterMatch(candidate, current symbolMatch) bool { return candidate.priority > current.priority } -func resolveMatches(matches []symbolMatch, called sets.Set[string]) resolution { +func resolveMatches(matches []symbolMatch, called sets.Set[*productionSymbol]) resolution { if len(matches) == 1 { match := matches[0] return resolution{kind: match.kind, target: match.symbol, priority: match.priority} } calledMatches := slices.DeleteFunc(slices.Clone(matches), func(match symbolMatch) bool { - return !called.Has(match.symbol.id) + return !called.Has(match.symbol) }) if len(calledMatches) == 1 { match := calledMatches[0] @@ -562,20 +573,19 @@ func resolveMatches(matches []symbolMatch, called sets.Set[string]) resolution { return resolution{kind: resolutionAmbiguous, candidates: candidates} } -func calledProductionSymbols(pass *analysis.Pass, test *ast.FuncDecl, symbolsByID map[string]*productionSymbol) sets.Set[string] { - called := sets.New[string]() +func calledProductionSymbols(pass *analysis.Pass, test *ast.FuncDecl, symbolsByFunc map[*types.Func]*productionSymbol) sets.Set[*productionSymbol] { + called := sets.New[*productionSymbol]() ast.Inspect(test.Body, func(node ast.Node) bool { call, ok := node.(*ast.CallExpr) if !ok { return true } fn := calledFunction(pass, call.Fun) - if fn == nil || fn.Pkg() == nil { + if fn == nil { return true } - id := symbolID(fn) - if _, ok := symbolsByID[id]; ok { - called.Insert(id) + if symbol := symbolsByFunc[functionOrigin(fn)]; symbol != nil { + called.Insert(symbol) } return true }) @@ -662,6 +672,25 @@ func reportGroup(pass *analysis.Pass, tests []*testFunction) { primary = tests[0] preferred = primary.name } + // The external test package can see both the local external test and the + // internal test recorded in the production package fact. If the external + // test is baselined and the internal canonical test is new, the internal + // pass cannot see the conflict. Report it on the new internal declaration + // so changed-line filtering evaluates the declaration that introduced it. + if primary != nil && primary.imported && !primary.suppressed { + for _, test := range tests { + if test.imported || !isLegacyException(pass, test) { + continue + } + pass.Report(analysis.Diagnostic{ + Pos: primary.namePos, + End: primary.nameEnd, + Message: fmt.Sprintf("test function %q adds another top-level test for %s while external test %q still exists; consolidate both under %q using table-driven cases or t.Run subtests", primary.name, symbol.display, test.name, preferred), + Related: relatedTest(test, fmt.Sprintf("external top-level test %q", test.name)), + }) + break + } + } if primary != nil && !shouldSuppress(pass, primary) { for _, test := range tests[1:] { if !test.imported && !isLegacyException(pass, test) { diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go index 1f25f73aef38..50e4901a1338 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go @@ -1,12 +1,15 @@ package testfuncstructure import ( + "go/ast" "go/parser" "go/token" + "path/filepath" "testing" . "github.com/onsi/gomega" + "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/analysistest" ) @@ -17,6 +20,7 @@ func TestRun(t *testing.T) { "a/legacyambiguous/production_test.go|TestWorkflow", "a/externallegacy/internal_test.go|TestValidateErrors", "a/externallegacy/internal_test.go|TestWorker_Run", + "a/reversecross/external_test.go|TestValidateErrors", // Explicit exceptions must take precedence over baseline accounting. "a/exception/production_test.go|TestWorker_Run", "a/externalexception/internal_test.go|TestWorker_Run", @@ -33,6 +37,7 @@ func TestRun(t *testing.T) { "a/split", "a/ambiguous", "a/external", + "a/identity", "a/legacy", "a/legacyambiguous", "a/precedence", @@ -67,6 +72,43 @@ func TestRun(t *testing.T) { } g.Expect(foundExternalPackage).To(BeTrue()) }) + t.Run("When an external scenario is baselined and a new internal canonical test is added, it should report the new internal declaration", func(t *testing.T) { + g := NewWithT(t) + results := analysistest.Run(&discardTesting{}, testdata, Analyzer, "a/reversecross") + + var externalResult *analysistest.Result + diagnosticCount := 0 + for _, result := range results { + diagnosticCount += len(result.Diagnostics) + if result.Pass.Pkg.Name() == "reversecross_test" { + externalResult = result + } + } + g.Expect(externalResult).ToNot(BeNil()) + g.Expect(diagnosticCount).To(Equal(1)) + g.Expect(externalResult.Diagnostics).To(HaveLen(1)) + + diagnostic := externalResult.Diagnostics[0] + g.Expect(diagnostic.Message).To(ContainSubstring(`while external test "TestValidateErrors" still exists`)) + position := externalResult.Pass.Fset.Position(diagnostic.Pos) + internalTestFile := filepath.Join(testdata, "src", "a", "reversecross", "internal_test.go") + g.Expect(position.Filename).To(Equal(internalTestFile)) + g.Expect(position.Line).To(Equal(declarationLine(t, internalTestFile, "TestValidate"))) + + g.Expect(diagnostic.Related).To(HaveLen(1)) + relatedPosition := externalResult.Pass.Fset.Position(diagnostic.Related[0].Pos) + g.Expect(relatedPosition.Filename).To(Equal(filepath.Join(testdata, "src", "a", "reversecross", "external_test.go"))) + g.Expect(relatedPosition.Line).To(Equal(declarationLine(t, relatedPosition.Filename, "TestValidateErrors"))) + + changedInternal := changedLineDiagnostics(externalResult, map[string]map[int]bool{ + position.Filename: {position.Line: true}, + }) + g.Expect(changedInternal).To(HaveLen(1)) + changedExternalOnly := changedLineDiagnostics(externalResult, map[string]map[int]bool{ + relatedPosition.Filename: {relatedPosition.Line: true}, + }) + g.Expect(changedExternalOnly).To(BeEmpty()) + }) t.Run("When a new test reuses a removed baseline path and name, it should still report the violation", func(t *testing.T) { analysistest.Run(t, testdata, Analyzer, "support/util") }) @@ -84,6 +126,42 @@ func TestRun(t *testing.T) { }) } +// analysistest matches // want comments against the package being analyzed. The +// reverse cross-package diagnostic intentionally points into the imported +// internal test file, so discard expectation mismatches and assert the result. +type discardTesting struct{} + +func (*discardTesting) Errorf(string, ...any) {} + +func declarationLine(t *testing.T, filename, name string) int { + t.Helper() + fileset := token.NewFileSet() + file, err := parser.ParseFile(fileset, filename, nil, 0) + if err != nil { + t.Fatalf("parse %s: %v", filename, err) + } + for _, declaration := range file.Decls { + function, ok := declaration.(*ast.FuncDecl) + if ok && function.Name.Name == name { + return fileset.Position(function.Name.Pos()).Line + } + } + t.Fatalf("declaration %s not found in %s", name, filename) + return 0 +} + +// changedLineDiagnostics mirrors golangci-lint's --new-from-rev position filter. +func changedLineDiagnostics(result *analysistest.Result, changed map[string]map[int]bool) []analysis.Diagnostic { + var diagnostics []analysis.Diagnostic + for _, diagnostic := range result.Diagnostics { + position := result.Pass.Fset.Position(diagnostic.Pos) + if changed[position.Filename][position.Line] { + diagnostics = append(diagnostics, diagnostic) + } + } + return diagnostics +} + func TestHasExcludedBuildTag(t *testing.T) { tests := []struct { name string diff --git a/skills/restructure-commits/ownership.json b/skills/restructure-commits/ownership.json index 297fde919fc8..fea45c333860 100644 --- a/skills/restructure-commits/ownership.json +++ b/skills/restructure-commits/ownership.json @@ -40,6 +40,9 @@ {"pattern": "cmd/install/assets/crds/external-dns/**", "group": "Vendor", "why": "marked as generated"}, {"pattern": "cmd/infra/aws/delegating_client.go", "group": "Vendor", "why": "marked as generated"}, {"pattern": "hack/tools/deadcode-report/testdata/reachability/generated.go", "group": "Vendor", "why": "marked as generated"}, + {"pattern": "hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/generated.go", "group": "Vendor", "why": "marked as generated"}, + {"pattern": "hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/good/generated_test.go", "group": "Vendor", "why": "marked as generated"}, + {"pattern": "hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/provenance/generated.go", "group": "Vendor", "why": "marked as generated"}, {"pattern": "support/awsapi/ec2.go", "group": "Vendor", "why": "marked as generated"}, {"pattern": "support/awsapi/elasticloadbalancing.go", "group": "Vendor", "why": "marked as generated"}, {"pattern": "support/awsapi/elasticloadbalancingv2.go", "group": "Vendor", "why": "marked as generated"}, From ff20ac2d18abd25023c9ae06715b0bdc04a09920 Mon Sep 17 00:00:00 2001 From: Bryan Cox Date: Wed, 28 Oct 2026 10:23:56 -0400 Subject: [PATCH 8/8] fix(lint): honor baselined reverse-cross tests Skip the reverse cross-package conflict when the internal canonical test is already baselined. Add regression fixtures for both-baselined cross-package tests and exported methods on unexported receiver types. --- .../analyzers/testfuncstructure/legacy.go | 2 +- .../testdata/src/a/bothlegacy/external_test.go | 11 +++++++++++ .../testdata/src/a/bothlegacy/internal_test.go | 7 +++++++ .../testdata/src/a/bothlegacy/production.go | 3 +++ .../src/a/unexportedreceiver/external_test.go | 15 +++++++++++++++ .../src/a/unexportedreceiver/production.go | 9 +++++++++ .../testfuncstructure/testfuncstructure.go | 2 +- .../testfuncstructure/testfuncstructure_test.go | 10 ++++++++++ 8 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/bothlegacy/external_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/bothlegacy/internal_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/bothlegacy/production.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unexportedreceiver/external_test.go create mode 100644 hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unexportedreceiver/production.go diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go index d60ffa52b818..5dcaff5d1d6c 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/legacy.go @@ -4,7 +4,7 @@ import "k8s.io/apimachinery/pkg/util/sets" // legacyExceptions contains exact pre-existing test declarations that do not // satisfy TESTING.md. Remove an entry when the corresponding test is migrated. -// Refreshed against main at 43bbac68e4b28539ae44e6ce4e82eb13518c2b05. +// Validated against main at 364a78f3d7001048a72e7c1c894c040bc7a43c04. var legacyExceptions = sets.Set[string]{ "api/util/ipnet/ipnet_test.go|TestUnmarshal": {}, "cmd/cluster/agent/create_test.go|TestCreateCluster": {}, diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/bothlegacy/external_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/bothlegacy/external_test.go new file mode 100644 index 000000000000..6e8568b4eada --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/bothlegacy/external_test.go @@ -0,0 +1,11 @@ +package bothlegacy_test + +import ( + "testing" + + "a/bothlegacy" +) + +func TestValidateErrors(t *testing.T) { + bothlegacy.Validate() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/bothlegacy/internal_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/bothlegacy/internal_test.go new file mode 100644 index 000000000000..608bb5583f38 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/bothlegacy/internal_test.go @@ -0,0 +1,7 @@ +package bothlegacy + +import "testing" + +func TestValidate(t *testing.T) { + Validate() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/bothlegacy/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/bothlegacy/production.go new file mode 100644 index 000000000000..6e254e4a2eb8 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/bothlegacy/production.go @@ -0,0 +1,3 @@ +package bothlegacy // want package:"testfuncstructure production symbols" + +func Validate() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unexportedreceiver/external_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unexportedreceiver/external_test.go new file mode 100644 index 000000000000..502fda9e3ff0 --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unexportedreceiver/external_test.go @@ -0,0 +1,15 @@ +package unexportedreceiver_test + +import ( + "testing" + + "a/unexportedreceiver" +) + +func TestWorker_Run(t *testing.T) { + unexportedreceiver.NewWorker().Run() +} + +func TestWorkerRun(t *testing.T) { // want `test function "TestWorkerRun" duplicates "TestWorker_Run" for worker.Run; consolidate scenarios under "TestWorker_Run" using table-driven cases or t.Run subtests` + unexportedreceiver.NewWorker().Run() +} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unexportedreceiver/production.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unexportedreceiver/production.go new file mode 100644 index 000000000000..9f470035fd6b --- /dev/null +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testdata/src/a/unexportedreceiver/production.go @@ -0,0 +1,9 @@ +package unexportedreceiver // want package:"testfuncstructure production symbols" + +type worker struct{} + +func NewWorker() *worker { + return &worker{} +} + +func (*worker) Run() {} diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go index 966c713d06c1..ff933970bdae 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure.go @@ -677,7 +677,7 @@ func reportGroup(pass *analysis.Pass, tests []*testFunction) { // test is baselined and the internal canonical test is new, the internal // pass cannot see the conflict. Report it on the new internal declaration // so changed-line filtering evaluates the declaration that introduced it. - if primary != nil && primary.imported && !primary.suppressed { + if primary != nil && primary.imported && !primary.suppressed && !isLegacyException(pass, primary) { for _, test := range tests { if test.imported || !isLegacyException(pass, test) { continue diff --git a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go index 50e4901a1338..58085650a25e 100644 --- a/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go +++ b/hack/tools/hypershiftlinter/analyzers/testfuncstructure/testfuncstructure_test.go @@ -21,6 +21,8 @@ func TestRun(t *testing.T) { "a/externallegacy/internal_test.go|TestValidateErrors", "a/externallegacy/internal_test.go|TestWorker_Run", "a/reversecross/external_test.go|TestValidateErrors", + "a/bothlegacy/internal_test.go|TestValidate", + "a/bothlegacy/external_test.go|TestValidateErrors", // Explicit exceptions must take precedence over baseline accounting. "a/exception/production_test.go|TestWorker_Run", "a/externalexception/internal_test.go|TestWorker_Run", @@ -37,6 +39,7 @@ func TestRun(t *testing.T) { "a/split", "a/ambiguous", "a/external", + "a/unexportedreceiver", "a/identity", "a/legacy", "a/legacyambiguous", @@ -112,6 +115,13 @@ func TestRun(t *testing.T) { t.Run("When a new test reuses a removed baseline path and name, it should still report the violation", func(t *testing.T) { analysistest.Run(t, testdata, Analyzer, "support/util") }) + t.Run("When internal and external split tests are both baselined, it should report no diagnostics", func(t *testing.T) { + g := NewWithT(t) + results := analysistest.Run(t, testdata, Analyzer, "a/bothlegacy") + for _, result := range results { + g.Expect(result.Diagnostics).To(BeEmpty()) + } + }) t.Run("When internal tests have explicit exceptions, it should allow an external canonical test and report remaining duplicates", func(t *testing.T) { analysistest.Run(t, testdata, Analyzer, "a/externalexception") })