-
Notifications
You must be signed in to change notification settings - Fork 70
🌱 OPRUN-4261 Migrate e2e tests to Godog BDD framework #2365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
🌱 OPRUN-4261 Migrate e2e tests to Godog BDD framework #2365
Conversation
✅ Deploy Preview for olmv1 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR migrates the e2e test suite from traditional Go testing framework to Godog (BDD/Cucumber framework), enabling behavior-driven development with Gherkin feature files. The migration maintains test coverage while reorganizing tests into feature files with step definitions.
Key Changes
- Replaced traditional Go test functions with Godog scenarios and step definitions
- Added Gherkin
.featurefiles describing test behavior in a more readable format - Introduced new test infrastructure (
steps.go,hooks.go) to support BDD testing
Reviewed changes
Copilot reviewed 19 out of 21 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| test/e2e/features_test.go | New test entry point initializing Godog suite with scenario and suite initializers |
| test/e2e/features/steps/steps.go | Implements step definitions mapping Gherkin steps to Go functions |
| test/e2e/features/steps/hooks.go | Provides scenario lifecycle hooks and feature gate detection |
| test/e2e/features/*.feature | Gherkin feature files defining test scenarios (install, update, recover, metrics) |
| test/e2e/features/steps/testdata/*.yaml | YAML templates for test resources (catalogs, RBAC) |
| test/e2e/*_test.go | Removed traditional test files migrated to feature files |
| test/e2e/network_policy_test.go | Added client initialization and helper function |
| go.mod, go.sum | Added Cucumber/Godog dependencies |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
730ba01 to
0482745
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2365 +/- ##
==========================================
- Coverage 73.07% 73.05% -0.03%
==========================================
Files 100 100
Lines 7641 7641
==========================================
- Hits 5584 5582 -2
- Misses 1622 1623 +1
- Partials 435 436 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
0482745 to
7038e17
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 20 out of 22 changed files in this pull request and generated 9 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
test/e2e/steps/steps.go
Outdated
| if stdErr := string(func() *exec.ExitError { | ||
| target := &exec.ExitError{} | ||
| _ = errors.As(err, &target) | ||
| return target | ||
| }().Stderr); !strings.Contains(stdErr, errMsg) { |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential nil pointer dereference when extracting stderr from error. If the error is not of type *exec.ExitError, this will panic. Consider adding a nil check:
waitFor(ctx, func() bool {
_, err := kubectlWithInput(yamlContent, "apply", "-f", "-")
if err == nil {
return false
}
var exitErr *exec.ExitError
if errors.As(err, &exitErr) && strings.Contains(string(exitErr.Stderr), errMsg) {
return true
}
return false
})| if stdErr := string(func() *exec.ExitError { | |
| target := &exec.ExitError{} | |
| _ = errors.As(err, &target) | |
| return target | |
| }().Stderr); !strings.Contains(stdErr, errMsg) { | |
| var exitErr *exec.ExitError | |
| if errors.As(err, &exitErr) { | |
| stdErr := string(exitErr.Stderr) | |
| if !strings.Contains(stdErr, errMsg) { | |
| return false | |
| } | |
| return true | |
| } | |
| return false |
test/e2e/steps/hooks.go
Outdated
| if _, err := kubectl("delete", r.kind, r.name, "-n", sc.namespace); err != nil { | ||
| logger.Info("Error deleting resource", "name", r.name, "namespace", sc.namespace, "stderr", string(err.(*exec.ExitError).Stderr)) |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential nil pointer dereference when asserting error type. If the error is not of type *exec.ExitError, this will panic when accessing .Stderr. Consider checking if the type assertion succeeded:
if _, err := kubectl("delete", r.kind, r.name, "-n", sc.namespace); err != nil {
var exitErr *exec.ExitError
stderr := ""
if errors.As(err, &exitErr) {
stderr = string(exitErr.Stderr)
}
logger.Info("Error deleting resource", "name", r.name, "namespace", sc.namespace, "stderr", stderr)
}| } | ||
|
|
||
| func scenarioCtx(ctx context.Context) *scenarioContext { | ||
| return ctx.Value(scenarioContextKey).(*scenarioContext) |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential nil pointer dereference. If ctx.Value(scenarioContextKey) returns nil, this will panic. Consider adding a nil check and returning a helpful error:
func scenarioCtx(ctx context.Context) *scenarioContext {
val := ctx.Value(scenarioContextKey)
if val == nil {
panic("scenario context not found in context")
}
sc, ok := val.(*scenarioContext)
if !ok {
panic("scenario context has wrong type")
}
return sc
}| return ctx.Value(scenarioContextKey).(*scenarioContext) | |
| val := ctx.Value(scenarioContextKey) | |
| if val == nil { | |
| panic("scenario context not found in context") | |
| } | |
| sc, ok := val.(*scenarioContext) | |
| if !ok { | |
| panic("scenario context has wrong type") | |
| } | |
| return sc |
7038e17 to
844eaa1
Compare
844eaa1 to
63e2440
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 20 out of 21 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
test/e2e/steps/hooks.go
Outdated
| logger.Info("Error deleting resource", "name", r.name, "namespace", sc.namespace, "stderr", string(func() *exec.ExitError { | ||
| target := &exec.ExitError{} | ||
| _ = errors.As(err, &target) | ||
| return target | ||
| }().Stderr)) |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential nil pointer dereference. If errors.As(err, &target) returns false, target will be nil and accessing target.Stderr will cause a panic.
Suggested fix:
var stderrStr string
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
stderrStr = string(exitErr.Stderr)
}
logger.Info("Error deleting resource", "name", r.name, "namespace", sc.namespace, "stderr", stderrStr)| logger.Info("Error deleting resource", "name", r.name, "namespace", sc.namespace, "stderr", string(func() *exec.ExitError { | |
| target := &exec.ExitError{} | |
| _ = errors.As(err, &target) | |
| return target | |
| }().Stderr)) | |
| var stderrStr string | |
| var exitErr *exec.ExitError | |
| if errors.As(err, &exitErr) { | |
| stderrStr = string(exitErr.Stderr) | |
| } | |
| logger.Info("Error deleting resource", "name", r.name, "namespace", sc.namespace, "stderr", stderrStr) |
joelanford
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a really nice improvement overall. Just some minor comments/questions.
| - "sleep" | ||
| args: | ||
| - "1000" | ||
| image: busybox:1.36 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the same image we've always used?
I recall there being issues in the past with rate limiting from Docker Hub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that is the one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we were hitting rate-limiting because we had the image untagged/set to latest, so it would pull every time no matter what. This is tagged so it should be fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true - we may want to copy it over to quay.io/operator-framework or something (assuming there's no licensing issue with that) to avoid those issues (not for this PR tho...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use registry.k8s.io/e2e-test-images/busybox:1.36.1-1 for downstream testing, it ought to be compatible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use
registry.k8s.io/e2e-test-images/busybox:1.36.1-1for downstream testing, it ought to be compatible.
I did not modify this part at all in this PR, see https://github.com/operator-framework/operator-controller/blob/main/test/e2e/cluster_extension_install_test.go#L722
| """ | ||
| Then ClusterExtension reports Progressing as True with Reason Retrying: | ||
| """ | ||
| error upgrading from currently installed version "1.0.0": no bundles found for package "test" matching version "1.2.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated to this PR, but something we need to fix separately. This message makes it sound like 1.2.0 just doesn't exist. But it does. It just isn't a successor of the currently installed version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created #2385
test/e2e/features_test.go
Outdated
| "github.com/spf13/pflag" | ||
| ctrl "sigs.k8s.io/controller-runtime" | ||
| //ctrllog "sigs.k8s.io/controller-runtime/pkg/log" | ||
| "sigs.k8s.io/controller-runtime/pkg/log/zap" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid new zap dependency? I think we're using klog in our main.go's. Can we use that here too?
test/e2e/steps/hooks.go
Outdated
|
|
||
| func ScenarioCleanup(ctx context.Context, _ *godog.Scenario, err error) (context.Context, error) { | ||
| sc := scenarioCtx(ctx) | ||
| for _, p := range sc.backGroundCmds { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kill and wait processes concurrently? Or does order matter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I think we do not need to wait at all.
test/e2e/steps/hooks.go
Outdated
| } | ||
| forDeletion = append(forDeletion, sc.addedResources...) | ||
| forDeletion = append(forDeletion, resource{name: sc.namespace, kind: "namespace"}) | ||
| for _, r := range forDeletion { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here: Can we delete objects concurrently?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could try, but not sure what we are gonna gain with it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deletion can sometimes take a little while if finalizers need to be processed. I assume we want foreground deletion so that we can be sure cleanup is complete before we move on. Seems like it could speed up cleanup considerably in those cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, we could do it.
test/e2e/features/install.feature
Outdated
| error for resolved bundle "single-namespace-operator.1.0.0" with version "1.0.0": | ||
| invalid ClusterExtension configuration: invalid configuration: required field "watchNamespace" is missing | ||
| """ | ||
| When ClusterExtension is updated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we could strategic merge patch instead and have a smaller yaml to make it more clear what's changing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be an improvement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we could strategic merge patch instead and have a smaller yaml to make it more clear what's changing?
We could ofcourse, we craft the grammar and the semantic: how would like to look like? If we would writing user docs, how this should be read by users?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the underlying user action would be something like kubectl patch - so maybe And user patches ClusterExtension with kubectl or something like that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would keep it declarative as much as possible, hence mentioning kubectl would not be good.
| And resource is applied | ||
| """ | ||
| apiVersion: v1 | ||
| kind: Namespace | ||
| metadata: | ||
| name: single-namespace-operator-target | ||
| """ | ||
| And ClusterExtension is applied |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just nothing the inconsistency here:
- resource is applied
vs - ClusterExtension is applied
The first one is generic, the second one is indicating a very specific resource. What's the reasoning behind this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first one is generic, the second one is indicating a very specific resource. What's the reasoning behind this?
Improved readability/focus on what matters. The fact that we use the same go code under the hood for both step is not important here - the reader should immediately understand what a step is about. Hence, we could even replace the generic step "resource is applied" with something like ([[:alnum:]]+) is applied or even ([[:alnum:]]+) is available so that we better document what is going on. Also, in this particular case, we could even create very concrete step namespace ([[:alnum:]]+) is available that is going to assure that the given namespace is created if not exists already.
test/e2e/features/install.feature
Outdated
| error for resolved bundle "single-namespace-operator.1.0.0" with version "1.0.0": | ||
| invalid ClusterExtension configuration: invalid configuration: required field "watchNamespace" is missing | ||
| """ | ||
| When ClusterExtension is updated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be an improvement.
test/e2e/steps/hooks.go
Outdated
| forDeletion = append(forDeletion, sc.addedResources...) | ||
| forDeletion = append(forDeletion, resource{name: sc.namespace, kind: "namespace"}) | ||
| for _, r := range forDeletion { | ||
| if _, err := kubectl("delete", r.kind, r.name, "-n", sc.namespace); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no distinction here between namespace-scoped resources (e.g. SAs), and cluster-scoped resources (e.g. CE). 'kubectl' may not complain about the -n argument for cluster-scoped resources, but it's basically wrong.
test/e2e/steps/steps.go
Outdated
| result := strings.ReplaceAll(content, "$TEST_NAMESPACE", sc.namespace) | ||
| result = strings.ReplaceAll(result, "$NAME", sc.clusterExtensionName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could see us wanting to expand this list, or even to make this a bit more dynamic. But probably not now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also noting an inconsistency in substitution mechanisms. Here it's $VAR, where as at: https://github.com/operator-framework/operator-controller/pull/2365/files#diff-37528e433a53ab946ef66fda327001b3a125c05c7ac9dfd2b49529fbfdc50cd3R378 it's {var}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also noting an inconsistency in substitution mechanisms. Here it's
$VAR, where as at: https://github.com/operator-framework/operator-controller/pull/2365/files#diff-37528e433a53ab946ef66fda327001b3a125c05c7ac9dfd2b49529fbfdc50cd3R378 it's{var}
IMO, bash-style like variables are understandable/known for a wider audience. We could use those in testdata templates as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think both are known, and in BASH, the recommendation is to use ${VAR} vs $VAR, so going with {VAR} is not that much different.
test/e2e/steps/steps.go
Outdated
| ) | ||
|
|
||
| func kubectl(args ...string) (string, error) { | ||
| cmd := exec.Command("kubectl", args...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we run these tests in other environments, we might need to consider the use of oc as a substitute for kubectl. Here and elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added --k8s.cli command arg that can tweak that.
|
Hi team, what’s the reason for choosing
|
@jianzhangbjz thanks for reaching out. I think I have summarized the motivation in the PR description, let me know if some of the points you raised are unanswered. IMO Ginko for e2e is still less readable for non-devs (and for devs as well) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 25 out of 26 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
test/e2e/README.md
Outdated
| ```go | ||
| func BundleInstalled(ctx context.Context, name, version string) error { | ||
| sc := scenarioCtx(ctx) | ||
| waitFor(ctx, func () bool { |
Copilot
AI
Dec 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in function signature - there's an extra space in 'func () bool'. Should be 'func() bool' without the space.
| waitFor(ctx, func () bool { | |
| waitFor(ctx, func() bool { |
test/e2e/README.md
Outdated
| All asynchronous operations use `waitFor` with consistent timeout (300s) and tick (1s): | ||
|
|
||
| ```go | ||
| waitFor(ctx, func () bool { |
Copilot
AI
Dec 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same typo as above - extra space in 'func () bool'.
| waitFor(ctx, func () bool { | |
| waitFor(ctx, func() bool { |
test/e2e/steps/steps.go
Outdated
| return fmt.Errorf("failed to apply RBAC configuration: %v: %s", err, string(func() *exec.ExitError { | ||
| target := &exec.ExitError{} | ||
| _ = errors.As(err, &target) | ||
| return target | ||
| }().Stderr)) |
Copilot
AI
Dec 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another instance of the same nil pointer dereference pattern with exec.ExitError type assertion on line 484.
| return fmt.Errorf("failed to apply RBAC configuration: %v: %s", err, string(func() *exec.ExitError { | |
| target := &exec.ExitError{} | |
| _ = errors.As(err, &target) | |
| return target | |
| }().Stderr)) | |
| var exitErr *exec.ExitError | |
| stderr := "" | |
| if errors.As(err, &exitErr) && exitErr != nil { | |
| stderr = string(exitErr.Stderr) | |
| } | |
| return fmt.Errorf("failed to apply RBAC configuration: %v: %s", err, stderr) |
test/e2e/steps/hooks.go
Outdated
| logger.Info("Error deleting resource", "name", r.name, "namespace", sc.namespace, "stderr", string(func() *exec.ExitError { | ||
| target := &exec.ExitError{} | ||
| _ = errors.As(err, &target) | ||
| return target | ||
| }().Stderr)) |
Copilot
AI
Dec 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another nil pointer dereference with the exec.ExitError type assertion on line 154.
| logger.Info("Error deleting resource", "name", r.name, "namespace", sc.namespace, "stderr", string(func() *exec.ExitError { | |
| target := &exec.ExitError{} | |
| _ = errors.As(err, &target) | |
| return target | |
| }().Stderr)) | |
| logger.Info("Error deleting resource", "name", r.name, "namespace", sc.namespace, "stderr", func() string { | |
| var exitErr *exec.ExitError | |
| if errors.As(err, &exitErr) && exitErr != nil { | |
| return string(exitErr.Stderr) | |
| } | |
| return "" | |
| }()) |
tmshort
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's generally fine, just a few small nits.
I think there's a semantic issue with "When", "Then" and "And", which doesn't matter in terms of the tests working, but instead how the tests are understood by humans.
It appeared to me that "When" represents a case where the tests does something (i.e. a test action), whereas "Then" and "And" represents a condition to be tested for.
This wasn't always consistent, as some actions seems to be under Then/Ands.
| return resp, nil | ||
| } | ||
|
|
||
| func SendMetricsRequest(ctx context.Context, serviceAccount string, endpoint string, controllerName string) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that his is using port-forwarding to scrape metrics, rather than a pod in the cluster (as the original test does).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, the advantage is that we do not need to pull an additional image + deploy another container.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, it's simply noting the change to the test process.
| func getComponentNamespace(t *testing.T, client, selector string) string { | ||
| cmd := exec.Command(client, "get", "pods", "--all-namespaces", "--selector="+selector, "--output=jsonpath={.items[0].metadata.namespace}") //nolint:gosec // just gathering pods for a given selector | ||
| output, err := cmd.CombinedOutput() | ||
| require.NoError(t, err, "Error determining namespace: %s", string(output)) | ||
|
|
||
| namespace := string(bytes.TrimSpace(output)) | ||
| if namespace == "" { | ||
| t.Fatal("No namespace found for selector " + selector) | ||
| } | ||
| return namespace | ||
| } | ||
|
|
||
| func init() { | ||
| cfg = ctrl.GetConfigOrDie() | ||
|
|
||
| var err error | ||
| utilruntime.Must(apiextensionsv1.AddToScheme(scheme.Scheme)) | ||
| c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) | ||
| utilruntime.Must(err) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it's just moving the function from a deleted file, that should be fine.
| When resource "deployment/test-operator" reports as ready | ||
| Then ClusterExtension is available |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But When seems to be more of an action the test takes, rather than the checking of a condition.
fffd715 to
a7f8e25
Compare
If there are multiple actions then scenario are structured like: When action1
And action2
And action3Similar for where there are multiple checks: Then check1
And check2
And check3 |
a7f8e25 to
8dac36a
Compare
There still seem to be inconsistencies, however, but I will have to go through them since the latest changes, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 25 out of 26 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
test/e2e/steps/steps.go
Outdated
| } | ||
| } | ||
|
|
||
| func k8scli(args ...string) (string, error) { |
Copilot
AI
Dec 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function name k8scli uses lowercase for CLI, which is inconsistent with typical Go naming conventions. Consider renaming to k8sCLI or k8sClient for better clarity and consistency.
| if err := portForwardCmd.Process.Kill(); err != nil { | ||
| return err | ||
| } | ||
| if _, err := portForwardCmd.Process.Wait(); err != nil { | ||
| return err | ||
| } |
Copilot
AI
Dec 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After killing the port-forward process, the code waits for it to exit. However, if Kill fails, the code still calls Wait on a process that might not exist or was not killed successfully, potentially causing a hang or panic. Check the error from Kill and handle it appropriately before calling Wait.
| Background: | ||
| Given OLM is available | ||
| And "test" catalog serves bundles | ||
| And Service account "olm-sa" with needed permissions is available in test namespace |
Copilot
AI
Dec 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Kubernetes context, "ServiceAccount" should be written as one word (PascalCase) to match the API resource type name.
| And Service account "olm-sa" with needed permissions is available in test namespace | |
| And ServiceAccount "olm-sa" with needed permissions is available in test namespace |
| } | ||
| podNameCmd := []string{"get", "pod", "-n", olmNamespace, "-o", "jsonpath={.items}"} | ||
| for k, v := range service.Spec.Selector { | ||
| podNameCmd = append(podNameCmd, fmt.Sprintf("--selector=%s=%s", k, v)) |
Copilot
AI
Dec 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The nolint:gosec comment disables security scanning but the justification is incomplete. The command builds a pod selector from service.Spec.Selector which comes from a service fetched from the cluster. While this is generally safe, a more specific justification would be helpful, such as "selector values are validated by Kubernetes API".
| podNameCmd = append(podNameCmd, fmt.Sprintf("--selector=%s=%s", k, v)) | |
| podNameCmd = append(podNameCmd, fmt.Sprintf("--selector=%s=%s", k, v)) //nolint:gosec // selector values are validated by Kubernetes API, so this is safe |
8dac36a to
1121604
Compare
|
The code looks ok, but when I run this locally on Fedora 42 x86_64 via ...and it finally finished, but the output comes out all at once, which isn't acceptable. We should really be able to see the progress of the test as it runs. Ah, it was terminated via SIGQUIT for taking too long... I rand this twice... I'd like someone else to run this locally to ensure it's not just me. |
1121604 to
c17e8d4
Compare
I can confirm you the same - I though first it was maybe because pushed the change. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 26 out of 27 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
test/e2e/steps/hooks.go
Outdated
| if c.Name == "manager" { | ||
| for _, arg := range c.Args { | ||
| if matches := featureGatePattern.FindStringSubmatch(arg); matches != nil { | ||
| v, _ := strconv.ParseBool(matches[2]) |
Copilot
AI
Dec 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error from strconv.ParseBool is ignored, which could lead to silent failures when parsing feature gate values. If parsing fails, the feature gate will remain at its default value without any indication of an error.
| v, _ := strconv.ParseBool(matches[2]) | |
| v, err := strconv.ParseBool(matches[2]) | |
| if err != nil { | |
| panic(fmt.Errorf("invalid boolean value for feature gate %q: %v", matches[1], err)) | |
| } |
test/e2e/steps/hooks.go
Outdated
| if err := json.Unmarshal([]byte(raw), &dl); err != nil { | ||
| return |
Copilot
AI
Dec 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error from json.Unmarshal is silently ignored with a return statement. If unmarshaling fails, the function continues with an empty deployment list, potentially missing the OLM deployment. Consider logging the error or handling it more explicitly.
| if err := portForwardCmd.Process.Kill(); err != nil { | ||
| return err |
Copilot
AI
Dec 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error returned from Process.Kill is not checked. If killing the process fails, the function continues to call Process.Wait on line 595, which could cause unexpected behavior or panics if the process handle is invalid.
| var ( | ||
| cfg *rest.Config | ||
| c client.Client | ||
| ) |
Copilot
AI
Dec 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The global variables cfg and c are declared here but were previously declared in e2e_suite_test.go (which is being removed). This creates duplicate declarations across the package. These variables should be consolidated or their scope reconsidered to avoid confusion.
test/e2e/steps/steps.go
Outdated
| } | ||
| sc.metricsResponse = make(map[string]string) | ||
| for _, p := range pods { | ||
| portForwardCmd := exec.Command(k8sCli, "port-forward", "-n", p.Namespace, fmt.Sprintf("pod/%s", p.Name), fmt.Sprintf("8443:%d", metricsPort)) //nolint:gosec // perfectly safe to start port-forwarder for provided controller name |
Copilot
AI
Dec 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The gosec warning is suppressed with a misleading comment. The command uses fmt.Sprintf to construct arguments including user-provided values (p.Name, metricsPort), which could be exploited if these values are not properly validated. The port comes from service.Spec.Ports which should be safe, but the pod name originates from the cluster and should be validated.
c17e8d4 to
f07ab4f
Compare
Replace traditional Go e2e tests with Godog (Cucumber for Go) to improve test readability and maintainability through behavior-driven development. Changes: - Convert existing test scenarios to Gherkin feature files - Implement reusable step definitions in steps/steps.go - Add scenario hooks for setup/teardown and feature gate detection - Provide comprehensive documentation in test/e2e/README.md - Remove legacy test files (cluster_extension_install_test.go, etc.) Benefits: - Human-readable test scenarios serve as living documentation - Better separation between test specification and implementation - Easier collaboration between technical and non-technical stakeholders - Reduced code duplication through reusable step definitions Assisted-By: "Claude <noreply@anthropic.com>"
f07ab4f to
bf10225
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 26 out of 27 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var ( | ||
| cfg *rest.Config | ||
| c client.Client | ||
| ) |
Copilot
AI
Dec 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The global variables cfg and c are redeclared in this file, shadowing the same variables that were previously declared in e2e_suite_test.go. Since e2e_suite_test.go has been removed, these variables are now initialized twice (once here and once in network_policy_test.go). Consider consolidating the initialization into a shared test setup file or package-level init function to avoid duplication and potential confusion.
| var ( | |
| cfg *rest.Config | |
| c client.Client | |
| ) | |
| // cfg and c are initialized in the shared test setup file (e.g., suite_test.go) |
| namespace string | ||
| clusterExtensionName string | ||
| removedResources []unstructured.Unstructured | ||
| backGroundCmds []*exec.Cmd |
Copilot
AI
Dec 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The field name backGroundCmds uses inconsistent casing - it should be either backgroundCmds (camelCase) or BackgroundCmds (if exported). The uppercase 'G' in the middle is non-standard Go naming convention.
| backGroundCmds []*exec.Cmd | |
| backgroundCmds []*exec.Cmd |
|
It's working in my local repo now.
|
|
/lgtm |
aaffdeb
into
operator-framework:main
Description
Replace traditional Go e2e tests with Godog (Cucumber for Go) to improve test readability and maintainability through behavior-driven development.
Benefits:
Changes:
Migration Notes
make test-e2eAssisted-By: Claude noreply@anthropic.com
Reviewer Checklist