Skip to content

Commit 18b8052

Browse files
tmshortclaude
andcommitted
tests: address CodeRabbit review comments from downstream PR
- Rename local variable `tag` to `bundleTag` in catalog.Build to avoid shadowing the method's `tag` parameter - Fail the step with an error on invalid LargeCRD field counts in the bundle option parser; use strconv.ParseInt with bitSize 0 to also reject non-positive values - Look up channels by name in TestCatalog_FBCGeneration instead of assuming insertion order - Use errgroup.Group with SetLimit(8) in ScenarioCleanup deletion loop to bound concurrent goroutines and kubectl calls - Clarify README that ClusterObjectSet deletion is conditional on the BoxcutterRuntime feature gate - Add missing `text` language specifier to fenced code block in e2e-isolation design doc - Remove extra blank line in setup.sh Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c8585e9 commit 18b8052

7 files changed

Lines changed: 45 additions & 30 deletions

File tree

docs/designs/testing/2026-04-13-e2e-isolation/design.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Each scenario dynamically builds and pushes its own bundle and catalog OCI image
1212
parameterized by scenario ID. All cluster-scoped resource names include the scenario ID, making
1313
conflicts structurally impossible.
1414

15-
```
15+
```text
1616
Scenario starts
1717
-> Generate parameterized bundle manifests (CRD names, deployments, etc. include scenario ID)
1818
-> Build + push bundle OCI images to e2e registry via go-containerregistry

test/e2e/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ Each scenario runs in its own namespace with unique resource names, ensuring com
292292

293293
The `ScenarioCleanup` hook ensures all resources are deleted after each scenario:
294294

295-
- Deletes ClusterExtensions and ClusterObjectSets
295+
- Deletes ClusterExtensions and (when BoxcutterRuntime gate is enabled) ClusterObjectSets
296296
- Deletes ClusterCatalogs
297297
- Deletes namespaces
298298
- Deletes added resources

test/e2e/steps/hooks.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import (
88
"os/exec"
99
"regexp"
1010
"strconv"
11-
"sync"
1211

1312
"github.com/cucumber/godog"
1413
"github.com/go-logr/logr"
1514
"github.com/spf13/pflag"
15+
"golang.org/x/sync/errgroup"
1616
appsv1 "k8s.io/api/apps/v1"
1717
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1818
"k8s.io/component-base/featuregate"
@@ -222,20 +222,20 @@ func ScenarioCleanup(ctx context.Context, _ *godog.Scenario, err error) (context
222222
}
223223
forDeletion = append(forDeletion, resource{name: sc.namespace, kind: "namespace"})
224224

225-
var wg sync.WaitGroup
225+
g := new(errgroup.Group)
226+
g.SetLimit(8)
226227
for _, r := range forDeletion {
227-
wg.Add(1)
228-
go func(res resource) {
229-
defer wg.Done()
230-
args := []string{"delete", res.kind, res.name, "--ignore-not-found=true"}
231-
if res.namespace != "" {
232-
args = append(args, "-n", res.namespace)
228+
g.Go(func() error {
229+
args := []string{"delete", r.kind, r.name, "--ignore-not-found=true"}
230+
if r.namespace != "" {
231+
args = append(args, "-n", r.namespace)
233232
}
234233
if _, err := k8sClient(args...); err != nil {
235-
logger.Info("Error deleting resource", "name", res.name, "namespace", res.namespace, "stderr", stderrOutput(err))
234+
logger.Info("Error deleting resource", "name", r.name, "namespace", r.namespace, "stderr", stderrOutput(err))
236235
}
237-
}(r)
236+
return nil
237+
})
238238
}
239-
wg.Wait()
239+
_ = g.Wait()
240240
return ctx, nil
241241
}

test/e2e/steps/steps.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,7 +1564,10 @@ func parseCatalogTable(table *godog.Table) ([]catalog.PackageOption, error) {
15641564
return nil, fmt.Errorf("duplicate bundle %s/%s: contents must be empty for repeated versions", pkg, version)
15651565
}
15661566
} else {
1567-
opts := parseContents(contents)
1567+
opts, err := parseContents(contents)
1568+
if err != nil {
1569+
return nil, fmt.Errorf("bundle %s/%s: %w", pkg, version, err)
1570+
}
15681571
bundleDefs[bk] = &bundleEntry{opts: opts}
15691572
bundleOrder = append(bundleOrder, bk)
15701573
}
@@ -1651,13 +1654,13 @@ spec:
16511654
return nil
16521655
}
16531656

1654-
func parseContents(contents string) []catalog.BundleOption {
1657+
func parseContents(contents string) ([]catalog.BundleOption, error) {
16551658
contents = strings.TrimSpace(contents)
16561659
if contents == "" {
1657-
return nil
1660+
return nil, nil
16581661
}
16591662
if strings.EqualFold(contents, "BadImage") {
1660-
return []catalog.BundleOption{catalog.BadImage()}
1663+
return []catalog.BundleOption{catalog.BadImage()}, nil
16611664
}
16621665
var opts []catalog.BundleOption
16631666
for _, part := range strings.Split(contents, ",") {
@@ -1682,10 +1685,11 @@ func parseContents(contents string) []catalog.BundleOption {
16821685
case strings.HasPrefix(part, "LargeCRD(") && strings.HasSuffix(part, ")"):
16831686
// LargeCRD(250)
16841687
countStr := part[len("LargeCRD(") : len(part)-1]
1685-
count, err := strconv.Atoi(countStr)
1686-
if err == nil {
1687-
opts = append(opts, catalog.WithLargeCRD(count))
1688+
count, err := strconv.ParseInt(countStr, 10, 0)
1689+
if err != nil || count <= 0 {
1690+
return nil, fmt.Errorf("invalid LargeCRD field count %q: must be a positive integer", countStr)
16881691
}
1692+
opts = append(opts, catalog.WithLargeCRD(int(count)))
16891693
case strings.HasPrefix(part, "ClusterRegistry(") && strings.HasSuffix(part, ")"):
16901694
// ClusterRegistry(mirrored-registry.operator-controller-e2e.svc.cluster.local:5000)
16911695
host := part[len("ClusterRegistry(") : len(part)-1]
@@ -1697,7 +1701,7 @@ func parseContents(contents string) []catalog.BundleOption {
16971701
opts = append(opts, catalog.StaticBundleDir(absDir))
16981702
}
16991703
}
1700-
return opts
1704+
return opts, nil
17011705
}
17021706

17031707
// PrometheusMetricsAreReturned validates that each pod's stored metrics response is non-empty and parses as valid Prometheus text format.

test/extension-developer-e2e/setup.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ reg_bundle_img="${cluster_registry_host}/bundles/registry-v1/registry-bundle:v0.
9292
# because docker push goes through the Docker daemon which
9393
# may be in a different network context (e.g. colima VM).
9494

95-
9695
###############################
9796
# Create the FBC that contains
9897
# the registry+v1 extensions

test/internal/catalog/catalog.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,9 @@ func (c *Catalog) Build(ctx context.Context, tag, localRegistry, clusterRegistry
154154
return nil, fmt.Errorf("failed to set bundle labels for %s:%s: %w", paramPkgName, bd.version, err)
155155
}
156156

157-
tag := fmt.Sprintf("%s/bundles/%s:v%s", localRegistry, paramPkgName, bd.version)
158-
if err := crane.Push(img, tag, crane.Insecure, crane.WithContext(ctx)); err != nil {
159-
return nil, fmt.Errorf("failed to push bundle image %s: %w", tag, err)
157+
bundleTag := fmt.Sprintf("%s/bundles/%s:v%s", localRegistry, paramPkgName, bd.version)
158+
if err := crane.Push(img, bundleTag, crane.Insecure, crane.WithContext(ctx)); err != nil {
159+
return nil, fmt.Errorf("failed to push bundle image %s: %w", bundleTag, err)
160160
}
161161
bundleClusterRegistry := clusterRegistry
162162
if spec.clusterRegistryOverride != "" {

test/internal/catalog/catalog_test.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,17 +134,29 @@ func TestCatalog_FBCGeneration(t *testing.T) {
134134
t.Errorf("expected 2 channels, got %d", len(pkg.channels))
135135
}
136136

137-
// Verify alpha channel has 1 entry
138-
alpha := pkg.channels[0]
139-
if alpha.name != "alpha" {
140-
t.Errorf("expected channel 'alpha', got %q", alpha.name)
137+
// Verify channels by name rather than by index so the test is robust to ordering changes.
138+
var alpha, beta *channelDef
139+
for i := range pkg.channels {
140+
switch pkg.channels[i].name {
141+
case "alpha":
142+
alpha = &pkg.channels[i]
143+
case "beta":
144+
beta = &pkg.channels[i]
145+
}
146+
}
147+
if alpha == nil {
148+
t.Fatal("alpha channel not found")
149+
}
150+
if beta == nil {
151+
t.Fatal("beta channel not found")
141152
}
153+
154+
// Verify alpha channel has 1 entry
142155
if len(alpha.entries) != 1 {
143156
t.Errorf("expected 1 alpha entry, got %d", len(alpha.entries))
144157
}
145158

146159
// Verify beta channel has replaces edge
147-
beta := pkg.channels[1]
148160
if len(beta.entries) != 2 {
149161
t.Fatalf("expected 2 beta entries, got %d", len(beta.entries))
150162
}

0 commit comments

Comments
 (0)