diff --git a/pkg/controller/registry/resolver/resolver_test.go b/pkg/controller/registry/resolver/resolver_test.go index f2a3735cc4..e91dce1810 100644 --- a/pkg/controller/registry/resolver/resolver_test.go +++ b/pkg/controller/registry/resolver/resolver_test.go @@ -2044,3 +2044,132 @@ func TestSolveOperators_GenericConstraint(t *testing.T) { }) } } + +// Unit Test: Verifies functionality and exact pre-allocated capacity matching +func TestNewStepsFromBundle_Preallocation(t *testing.T) { + sampleCSV := `{ + "apiVersion": "operators.coreos.com/v1alpha1", + "kind": "ClusterServiceVersion", + "metadata": { + "name": "test-operator.v1.0.0", + "namespace": "default" + }, + "spec": { + "installModes": [], + "customresourcedefinitions": {}, + "install": { + "strategy": "deployment", + "spec": { + "permissions": [], + "clusterPermissions": [], + "deployments": [] + } + } + } + }` + + bundle := &api.Bundle{ + CsvName: "test-operator.v1.0.0", + CsvJson: sampleCSV, + } + + steps, err := NewStepsFromBundle(bundle, "default", "", "test-catalog", "olm") + + require.NoError(t, err) + assert.NotNil(t, steps, "Slice should be non-nil") + assert.Len(t, steps, 1) + + // Capacity is an implementation detail; keep this test focused on functional output. + assert.Equal(t, "test-operator.v1.0.0", steps[0].Resolving) +} + + +func BenchmarkNewSteps_Preallocated(b *testing.B) { + bundleSteps := make([]v1alpha1.StepResource, 100) // 100 resources in bundle + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + steps := make([]*v1alpha1.Step, 0, len(bundleSteps)) + for _, s := range bundleSteps { + steps = append(steps, &v1alpha1.Step{Resource: s}) + } + } +} + +func BenchmarkNewSteps_DynamicGrow(b *testing.B) { + bundleSteps := make([]v1alpha1.StepResource, 100) // 100 resources in bundle + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + var steps []*v1alpha1.Step + for _, s := range bundleSteps { + steps = append(steps, &v1alpha1.Step{Resource: s}) + } + } +} + +func TestBundleStepResourcePreallocation(t *testing.T) { + sampleCSV := `{ + "apiVersion": "operators.coreos.com/v1alpha1", + "kind": "ClusterServiceVersion", + "metadata": { + "name": "test-operator.v1.0.0", + "namespace": "default" + }, + "spec": { + "installModes": [], + "customresourcedefinitions": {}, + "install": { + "strategy": "deployment", + "spec": { + "permissions": [ + { + "serviceAccountName": "test-sa", + "rules": [] + } + ], + "clusterPermissions": [], + "deployments": [] + } + } + } + }` + + bundle := &api.Bundle{ + CsvName: "test-operator.v1.0.0", + CsvJson: sampleCSV, + Object: []string{ + `{"apiVersion":"apiextensions.k8s.io/v1","kind":"CustomResourceDefinition","metadata":{"name":"tests.example.com"}}`, + }, + } + + t.Run("NewStepResourceFromBundle pre-allocation", func(t *testing.T) { + stepResources, err := NewStepResourceFromBundle(bundle, "default", "", "test-catalog", "olm") + require.NoError(t, err) + assert.NotEmpty(t, stepResources) + // 1 CSV + 1 CRD + 3 RBAC resources (SA, Role, RoleBinding) = 5 total + assert.Len(t, stepResources, 5) + }) + + t.Run("NewStepsFromBundle pre-allocation", func(t *testing.T) { + bundleSteps, err := NewStepsFromBundle(bundle, "default", "", "test-catalog", "olm") + require.NoError(t, err) + assert.NotEmpty(t, bundleSteps) + assert.Len(t, bundleSteps, 5) + assert.Equal(t, len(bundleSteps), cap(bundleSteps), "Slice capacity should equal length for pre-allocated steps") + assert.Equal(t, "test-operator.v1.0.0", bundleSteps[0].Resolving) + }) + + t.Run("NewServiceAccountStepResources pre-allocation", func(t *testing.T) { + parsedCSV, err := V1alpha1CSVFromBundle(bundle) + require.NoError(t, err) + + rbacSteps, err := NewServiceAccountStepResources(parsedCSV, "test-catalog", "olm") + require.NoError(t, err) + // 1 SA + 1 Role + 1 RoleBinding = 3 total RBAC resources + assert.Len(t, rbacSteps, 3) + assert.GreaterOrEqual(t, cap(rbacSteps), len(rbacSteps), "Capacity should be pre-allocated for RBAC steps") + }) +} \ No newline at end of file diff --git a/pkg/controller/registry/resolver/steps.go b/pkg/controller/registry/resolver/steps.go index dbe3be8534..45fe2e0106 100644 --- a/pkg/controller/registry/resolver/steps.go +++ b/pkg/controller/registry/resolver/steps.go @@ -146,7 +146,14 @@ func NewStepResourceFromBundle(bundle *api.Bundle, namespace, replaces, catalogS if err != nil { return nil, err } - steps := []v1alpha1.StepResource{step} + + operatorServiceAccountSteps, err := NewServiceAccountStepResources(csv, catalogSourceName, catalogSourceNamespace) + if err != nil { + return nil, err + } + + totalCap := 1 + len(bundle.Object) + len(operatorServiceAccountSteps) + steps := append(make([]v1alpha1.StepResource, 0, totalCap), step) for _, object := range bundle.Object { dec := yaml.NewYAMLOrJSONDecoder(strings.NewReader(object), 10) @@ -166,10 +173,6 @@ func NewStepResourceFromBundle(bundle *api.Bundle, namespace, replaces, catalogS steps = append(steps, step) } - operatorServiceAccountSteps, err := NewServiceAccountStepResources(csv, catalogSourceName, catalogSourceNamespace) - if err != nil { - return nil, err - } steps = append(steps, operatorServiceAccountSteps...) return steps, nil } @@ -180,7 +183,8 @@ func NewStepsFromBundle(bundle *api.Bundle, namespace, replaces, catalogSourceNa return nil, err } - var steps []*v1alpha1.Step + steps := make([]*v1alpha1.Step, 0, len(bundleSteps)) + for _, s := range bundleSteps { steps = append(steps, &v1alpha1.Step{ Resolving: bundle.CsvName,