-
Notifications
You must be signed in to change notification settings - Fork 4.8k
CNTRLPLANE-2449: test(sig-builds][Feature:Builds]): ignore watchapi.Bookmark as they are synthetic and used only for signaling sync completion #30885
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
Changes from all commits
620cf35
1cb0ef5
7554a41
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ import ( | |
|
|
||
| "k8s.io/apimachinery/pkg/labels" | ||
| "k8s.io/apimachinery/pkg/util/validation" | ||
| watchapi "k8s.io/apimachinery/pkg/watch" | ||
|
|
||
| g "github.com/onsi/ginkgo/v2" | ||
| o "github.com/onsi/gomega" | ||
|
|
@@ -82,6 +83,14 @@ var _ = g.Describe("[sig-builds][Feature:Builds][Slow] using build configuration | |
| // Wait for it to become running | ||
| for { | ||
| event := <-buildWatch.ResultChan() | ||
| // Ignore bookmark events from WatchList protocol. | ||
| // When watching with an empty ResourceVersion, the apiserver uses WatchList and sends | ||
| // bookmark events to signal sync completion. These are synthetic events (not actual | ||
| // Build objects) used for caching coordination, so we skip them and continue waiting | ||
| // for real Build events. | ||
| if event.Type == watchapi.Bookmark { | ||
| continue | ||
| } | ||
| build := event.Object.(*buildv1.Build) | ||
| o.Expect(IsBuildComplete(build)).Should(o.BeFalse()) | ||
|
Comment on lines
85
to
95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, verify the file exists and check its general structure
wc -l test/extended/builds/run_policy.goRepository: openshift/origin Length of output: 98 🏁 Script executed: # Check the imports at the top of the file to verify watchapi import
head -20 test/extended/builds/run_policy.goRepository: openshift/origin Length of output: 468 🏁 Script executed: # Now examine the specific line ranges mentioned in the review
# Check lines 85-95 first
sed -n '80,100p' test/extended/builds/run_policy.go | cat -nRepository: openshift/origin Length of output: 1091 🏁 Script executed: # Check all 8 locations to confirm the pattern is repeated
# Lines 111-121
sed -n '106,126p' test/extended/builds/run_policy.go | cat -nRepository: openshift/origin Length of output: 892 🏁 Script executed: # Lines 182-192
sed -n '177,197p' test/extended/builds/run_policy.go | cat -nRepository: openshift/origin Length of output: 1277 🏁 Script executed: # Lines 262-272
sed -n '257,277p' test/extended/builds/run_policy.go | cat -nRepository: openshift/origin Length of output: 1260 🏁 Script executed: # Lines 322-332
sed -n '317,337p' test/extended/builds/run_policy.go | cat -nRepository: openshift/origin Length of output: 1281 🏁 Script executed: # Lines 410-420
sed -n '405,425p' test/extended/builds/run_policy.go | cat -nRepository: openshift/origin Length of output: 1271 🏁 Script executed: # Lines 478-488
sed -n '473,493p' test/extended/builds/run_policy.go | cat -nRepository: openshift/origin Length of output: 1141 🏁 Script executed: # Lines 509-519
sed -n '504,524p' test/extended/builds/run_policy.go | cat -nRepository: openshift/origin Length of output: 1213 Handle closed channels and error events before casting to At lines 86, 112, 183, 263, 323, 411, 479, and 510, the code receives from Suggested pattern (apply to each loop)- event := <-buildWatch.ResultChan()
+ event, ok := <-buildWatch.ResultChan()
+ o.Expect(ok).To(o.BeTrue(), "build watch channel closed unexpectedly")
if event.Type == watchapi.Bookmark {
continue
}
- build := event.Object.(*buildv1.Build)
+ o.Expect(event.Type).NotTo(o.Equal(watchapi.Error), "received watch error event")
+ build, ok := event.Object.(*buildv1.Build)
+ o.Expect(ok).To(o.BeTrue(), "unexpected watch object type: %T", event.Object)Also applies to: 112, 183, 263, 323, 411, 479, 510 🤖 Prompt for AI Agents |
||
| if build.Name == startedBuilds[0] && build.Status.Phase == buildv1.BuildPhaseRunning { | ||
|
|
@@ -100,6 +109,14 @@ var _ = g.Describe("[sig-builds][Feature:Builds][Slow] using build configuration | |
|
|
||
| for { | ||
| event := <-buildWatch.ResultChan() | ||
| // Ignore bookmark events from WatchList protocol. | ||
| // When watching with an empty ResourceVersion, the apiserver uses WatchList and sends | ||
| // bookmark events to signal sync completion. These are synthetic events (not actual | ||
| // Build objects) used for caching coordination, so we skip them and continue waiting | ||
| // for real Build events. | ||
| if event.Type == watchapi.Bookmark { | ||
| continue | ||
| } | ||
| build := event.Object.(*buildv1.Build) | ||
| if build.Name == startedBuilds[0] { | ||
| if IsBuildComplete(build) { | ||
|
|
@@ -163,6 +180,14 @@ var _ = g.Describe("[sig-builds][Feature:Builds][Slow] using build configuration | |
| sawCompletion := false | ||
| for { | ||
| event := <-buildWatch.ResultChan() | ||
| // Ignore bookmark events from WatchList protocol. | ||
| // When watching with an empty ResourceVersion, the apiserver uses WatchList and sends | ||
| // bookmark events to signal sync completion. These are synthetic events (not actual | ||
| // Build objects) used for caching coordination, so we skip them and continue waiting | ||
| // for real Build events. | ||
| if event.Type == watchapi.Bookmark { | ||
| continue | ||
| } | ||
| build := event.Object.(*buildv1.Build) | ||
| var lastCompletion time.Time | ||
| if build.Status.Phase == buildv1.BuildPhaseComplete { | ||
|
|
@@ -235,6 +260,14 @@ var _ = g.Describe("[sig-builds][Feature:Builds][Slow] using build configuration | |
| var cancelTime, cancelTime2 time.Time | ||
| for { | ||
| event := <-buildWatch.ResultChan() | ||
| // Ignore bookmark events from WatchList protocol. | ||
| // When watching with an empty ResourceVersion, the apiserver uses WatchList and sends | ||
| // bookmark events to signal sync completion. These are synthetic events (not actual | ||
| // Build objects) used for caching coordination, so we skip them and continue waiting | ||
| // for real Build events. | ||
| if event.Type == watchapi.Bookmark { | ||
| continue | ||
| } | ||
| build := event.Object.(*buildv1.Build) | ||
| if build.Status.Phase == buildv1.BuildPhasePending || build.Status.Phase == buildv1.BuildPhaseRunning { | ||
| if build.Status.Phase == buildv1.BuildPhaseRunning { | ||
|
|
@@ -287,6 +320,14 @@ var _ = g.Describe("[sig-builds][Feature:Builds][Slow] using build configuration | |
| for done == false { | ||
| select { | ||
| case event := <-buildWatch.ResultChan(): | ||
| // Ignore bookmark events from WatchList protocol. | ||
| // When watching with an empty ResourceVersion, the apiserver uses WatchList and sends | ||
| // bookmark events to signal sync completion. These are synthetic events (not actual | ||
| // Build objects) used for caching coordination, so we skip them and continue waiting | ||
| // for real Build events. | ||
| if event.Type == watchapi.Bookmark { | ||
| continue | ||
| } | ||
| build := event.Object.(*buildv1.Build) | ||
| if build.Status.Phase == buildv1.BuildPhasePending { | ||
| o.Expect(hasConditionState(build, buildv1.BuildPhasePending, true)).Should(o.BeTrue()) | ||
|
|
@@ -367,6 +408,14 @@ var _ = g.Describe("[sig-builds][Feature:Builds][Slow] using build configuration | |
| for !done { | ||
| select { | ||
| case event := <-buildWatch.ResultChan(): | ||
| // Ignore bookmark events from WatchList protocol. | ||
| // When watching with an empty ResourceVersion, the apiserver uses WatchList and sends | ||
| // bookmark events to signal sync completion. These are synthetic events (not actual | ||
| // Build objects) used for caching coordination, so we skip them and continue waiting | ||
| // for real Build events. | ||
| if event.Type == watchapi.Bookmark { | ||
| continue | ||
| } | ||
| build := event.Object.(*buildv1.Build) | ||
| if build.Status.Phase == buildv1.BuildPhasePending || build.Status.Phase == buildv1.BuildPhaseRunning { | ||
| if build.Status.Phase == buildv1.BuildPhaseRunning { | ||
|
|
@@ -427,6 +476,14 @@ var _ = g.Describe("[sig-builds][Feature:Builds][Slow] using build configuration | |
| // Wait for the first build to become running | ||
| for { | ||
| event := <-buildWatch.ResultChan() | ||
| // Ignore bookmark events from WatchList protocol. | ||
| // When watching with an empty ResourceVersion, the apiserver uses WatchList and sends | ||
| // bookmark events to signal sync completion. These are synthetic events (not actual | ||
| // Build objects) used for caching coordination, so we skip them and continue waiting | ||
| // for real Build events. | ||
| if event.Type == watchapi.Bookmark { | ||
| continue | ||
| } | ||
| build := event.Object.(*buildv1.Build) | ||
| if build.Name == startedBuilds[0] { | ||
| if build.Status.Phase == buildv1.BuildPhaseRunning { | ||
|
|
@@ -450,6 +507,14 @@ var _ = g.Describe("[sig-builds][Feature:Builds][Slow] using build configuration | |
| // will be the last build created. | ||
| for { | ||
| event := <-buildWatch.ResultChan() | ||
| // Ignore bookmark events from WatchList protocol. | ||
| // When watching with an empty ResourceVersion, the apiserver uses WatchList and sends | ||
| // bookmark events to signal sync completion. These are synthetic events (not actual | ||
| // Build objects) used for caching coordination, so we skip them and continue waiting | ||
| // for real Build events. | ||
| if event.Type == watchapi.Bookmark { | ||
| continue | ||
| } | ||
| build := event.Object.(*buildv1.Build) | ||
| e2e.Logf("got event for build %s with phase %s", build.Name, build.Status.Phase) | ||
| // The second build should be cancelled | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ import ( | |
|
|
||
| corev1 "k8s.io/api/core/v1" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| watchapi "k8s.io/apimachinery/pkg/watch" | ||
| "k8s.io/client-go/rest" | ||
| admissionapi "k8s.io/pod-security-admission/api" | ||
|
|
||
|
|
@@ -335,6 +336,14 @@ Loop: | |
| case <-time.After(10 * time.Second): | ||
| t.Fatalf("timed out waiting for build event") | ||
| case event := <-watch.ResultChan(): | ||
| // Ignore bookmark events from WatchList protocol. | ||
| // When watching with an empty ResourceVersion, the apiserver uses WatchList and sends | ||
| // bookmark events to signal sync completion. These are synthetic events (not actual | ||
| // Build objects) used for caching coordination, so we skip them and continue waiting | ||
| // for real Build events. | ||
| if event.Type == watchapi.Bookmark { | ||
| continue | ||
| } | ||
| actual := event.Object.(*buildv1.Build) | ||
|
Comment on lines
338
to
347
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify unsafe direct type assertions after watch.ResultChan() reads.
# Expected result: occurrences in this file where event.Object is asserted as *buildv1.Build
# without guarding channel closure/object type first.
rg -nP --type=go -C3 'case\s+\w+\s*:=\s*<-\s*\w+\.ResultChan\(\)|\.\s*Object\.\(\*buildv1\.Build\)' test/extended/builds/webhook.goRepository: openshift/origin Length of output: 1269 🏁 Script executed: # Search for all watch.ResultChan() usages in the file
rg -n 'ResultChan\(\)' test/extended/builds/webhook.goRepository: openshift/origin Length of output: 144 🏁 Script executed: # Search for all event.Object type assertions or accesses
rg -n 'event\.Object' test/extended/builds/webhook.goRepository: openshift/origin Length of output: 153 🏁 Script executed: # Get the full context around both flagged sections to understand the watch handling
sed -n '330,410p' test/extended/builds/webhook.goRepository: openshift/origin Length of output: 3201 Guard watch channel closure and type assertions to prevent panics and flaky test failures. The code at lines 338–347 and 395–403 reads from Proposed fix@@
- case event := <-watch.ResultChan():
+ case event, ok := <-watch.ResultChan():
+ if !ok {
+ t.Fatalf("build watch channel closed")
+ }
// Ignore bookmark events from WatchList protocol.
@@
if event.Type == watchapi.Bookmark {
continue
}
- actual := event.Object.(*buildv1.Build)
+ if event.Type == watchapi.Error {
+ t.Fatalf("watch returned error event: %#v", event.Object)
+ }
+ actual, ok := event.Object.(*buildv1.Build)
+ if !ok {
+ continue
+ }
@@
- case event := <-watch.ResultChan():
+ case event, ok := <-watch.ResultChan():
+ if !ok {
+ t.Fatalf("build watch channel closed")
+ }
// Ignore bookmark events from WatchList protocol.
@@
if event.Type == watchapi.Bookmark {
continue
}
- build := event.Object.(*buildv1.Build)
+ if event.Type == watchapi.Error {
+ t.Fatalf("watch returned error event: %#v", event.Object)
+ }
+ build, ok := event.Object.(*buildv1.Build)
+ if !ok {
+ continue
+ }
t.Fatalf("Unexpected build created: %#v", build)🤖 Prompt for AI Agents |
||
| t.Logf("Saw build object %#v", actual) | ||
| if actual.Status.Phase != buildv1.BuildPhasePending { | ||
|
|
@@ -377,12 +386,24 @@ func TestWebhookGitHubPing(t g.GinkgoTInterface, oc *exutil.CLI) { | |
|
|
||
| // TODO: improve negative testing | ||
| timer := time.NewTimer(time.Second * 5) | ||
| select { | ||
| case <-timer.C: | ||
| // nothing should happen | ||
| case event := <-watch.ResultChan(): | ||
| build := event.Object.(*buildv1.Build) | ||
| t.Fatalf("Unexpected build created: %#v", build) | ||
| Loop: | ||
| for { | ||
| select { | ||
| case <-timer.C: | ||
| // nothing should happen - test passed | ||
| break Loop | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the reason of this change?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Previously there was only a |
||
| case event := <-watch.ResultChan(): | ||
| // Ignore bookmark events from WatchList protocol. | ||
| // When watching with an empty ResourceVersion, the apiserver uses WatchList and sends | ||
| // bookmark events to signal sync completion. These are synthetic events (not actual | ||
| // Build objects) used for caching coordination, so they should not be treated as | ||
| // unexpected builds. | ||
| if event.Type == watchapi.Bookmark { | ||
| continue | ||
| } | ||
| build := event.Object.(*buildv1.Build) | ||
| t.Fatalf("Unexpected build created: %#v", build) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
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.
🧩 Analysis chain
🏁 Script executed:
Repository: openshift/origin
Length of output: 97
🏁 Script executed:
Repository: openshift/origin
Length of output: 99
🏁 Script executed:
Repository: openshift/origin
Length of output: 1333
🏁 Script executed:
Repository: openshift/origin
Length of output: 1316
🏁 Script executed:
Repository: openshift/origin
Length of output: 316
🏁 Script executed:
Repository: openshift/origin
Length of output: 324
Move timeout outside loops to ensure absolute timeout, not per-iteration reset.
Lines 146 and 401 create
time.After(BuildControllersWatchTimeout)inside select statements within loops. With frequent bookmark events, each iteration resets the timeout to its full duration; the effective timeout becomes per-iteration rather than absolute. This can cause indefinite hangs when bookmark traffic is sustained. Line 394'si--logic inwaitForWatchTypecompounds the issue by preventing progress while timeout keeps resetting.Suggested fix
func waitForWatch(t testingT, name string, w watchapi.Interface) *watchapi.Event { + timeout := time.After(BuildControllersWatchTimeout) for { select { case e, ok := <-w.ResultChan(): if !ok { t.Fatalf("Channel closed waiting for watch: %s", name) } if e.Type == watchapi.Bookmark { continue } return &e - case <-time.After(BuildControllersWatchTimeout): + case <-timeout: t.Fatalf("Timed out waiting for watch: %s", name) return nil } } } @@ func waitForWatchType(t testingT, name string, w watchapi.Interface, expect watchapi.EventType) *watchapi.Event { - tries := 10 - for i := 0; i < tries; i++ { + tries := 10 + timeout := time.After(BuildControllersWatchTimeout) + seen := 0 + for seen < tries { select { - case e := <-w.ResultChan(): + case e, ok := <-w.ResultChan(): + if !ok { + t.Fatalf("Channel closed waiting for watch: %s", name) + } if e.Type == watchapi.Bookmark { - i-- // Don't consume a try for bookmark events continue } + seen++ if e.Type != expect { continue } return &e - case <-time.After(BuildControllersWatchTimeout): + case <-timeout: t.Fatalf("Timed out waiting for watch: %s", name) return nil } } t.Fatalf("Waited for a %v event with %d tries but never received one", expect, tries) return nil }🤖 Prompt for AI Agents