Skip to content

Conversation

@gangwgr
Copy link

@gangwgr gangwgr commented Oct 16, 2025

Adding test cases for "Add support for event-ttl in Kube API Server Operator"
Create separate Go module for test extension
Move test code and metadata to tests-extension directory
Update build infrastructure for isolated test module
Remove test dependencies from root go.mod

Assisted-by: Claude
test run

./cluster-kube-apiserver-operator-tests-ext run-test "[Jira:kube-apiserver][sig-api-machinery][FeatureGate:EventTTL] Event TTL Configuration should configure and validate eventTTLMinutes=5m [Serial][Disruptive][Slow][Suite:openshift/cluster-kube-apiserver-operator/conformance/serial]"
  Running Suite:  - /Users/rgangwar/Downloads/backupoffice/cluster-kube-apiserver-operator
  ========================================================================================
  Random Seed: 1760683956 - will randomize all specs

  Will run 1 of 1 specs
  ------------------------------
  [Jira:kube-apiserver][sig-api-machinery][FeatureGate:EventTTL] Event TTL Configuration should configure and validate eventTTLMinutes=5m [Disruptive][Slow][Suite:openshift/cluster-kube-apiserver-operator/conformance/serial]
  github.com/openshift/cluster-kube-apiserver-operator/test/extended/event_ttl.go:53
Found configuration for host https://xxxxxxxxxxxxxxx.
    STEP: === Starting test for eventTTLMinutes=5 at 2025-10-17T12:22:36+05:30 === @ 10/17/25 12:22:36.853
    STEP: Step 1: Checking if EventTTL feature gate is present @ 10/17/25 12:22:36.853
    STEP:   Current FeatureSet: TechPreviewNoUpgrade @ 10/17/25 12:22:37.976
    STEP: [OK] EventTTL feature gate found and is already enabled @ 10/17/25 12:22:37.976
    STEP: 
    Step 2: Configuring eventTTLMinutes=5 in KubeAPIServer CR @ 10/17/25 12:22:37.976
    STEP:   Patch data: {"spec":{"eventTTLMinutes":5}} @ 10/17/25 12:22:37.976
    STEP: [OK] eventTTLMinutes=5 configured at 2025-10-17T12:22:38+05:30 @ 10/17/25 12:22:38.534
    STEP: Step 3: Waiting for new revision to roll out (timeout: 20 minutes)... @ 10/17/25 12:22:38.534
    STEP:   Rollout started at: 2025-10-17T12:22:38+05:30 @ 10/17/25 12:22:38.534
    STEP:   Initial state: 3 pods, oldest created at 2025-10-17T11:24:09+05:30, initial revision: 11 @ 10/17/25 12:22:39
    STEP:   [Attempt 1] All 3 pods are running. New pods: 0/0. Revision: 11 @ 10/17/25 12:22:54.447
    STEP:   [Attempt 4] All 3 pods are running. New pods: 0/0. Revision: 11 @ 10/17/25 12:23:52.5
    STEP:   [Attempt 8] Error listing pods: Get "https://xxxxxxxxxxxxxxx/api/v1/namespaces/openshift-kube-apiserver/pods?labelSelector=app%3Dopenshift-kube-apiserver%2Capiserver%3Dtrue": dial tcp: lookup xxxxxxxxxxxxxxx: i/o timeout - error from a previous attempt: http2: client connection lost @ 10/17/25 12:25:36.037
    STEP:   [Attempt 9] Error listing pods: Get "https://xxxxxxxxxxxxxxx/api/v1/namespaces/openshift-kube-apiserver/pods?labelSelector=app%3Dopenshift-kube-apiserver%2Capiserver%3Dtrue": dial tcp: lookup xxxxxxxxxxxxxxx: i/o timeout @ 10/17/25 12:26:06.039
    STEP:   [Attempt 10] Error listing pods: Get "https://xxxxxxxxxxxxxxx/api/v1/namespaces/openshift-kube-apiserver/pods?labelSelector=app%3Dopenshift-kube-apiserver%2Capiserver%3Dtrue": net/http: TLS handshake timeout @ 10/17/25 12:26:44.198
    STEP:   [Attempt 11] Error listing pods: Get "https://xxxxxxxxxxxxxxx/api/v1/namespaces/openshift-kube-apiserver/pods?labelSelector=app%3Dopenshift-kube-apiserver%2Capiserver%3Dtrue": net/http: TLS handshake timeout @ 10/17/25 12:26:54.284
    STEP:   [Attempt 12] Error listing pods: Get "https://xxxxxxxxxxxxxxx/api/v1/namespaces/openshift-kube-apiserver/pods?labelSelector=app%3Dopenshift-kube-apiserver%2Capiserver%3Dtrue": net/http: TLS handshake timeout @ 10/17/25 12:27:04.637
    STEP:   [Attempt 13] Error listing pods: Get "https://xxxxxxxxxxxxxxx/api/v1/namespaces/openshift-kube-apiserver/pods?labelSelector=app%3Dopenshift-kube-apiserver%2Capiserver%3Dtrue": net/http: TLS handshake timeout @ 10/17/25 12:27:33.575
    STEP:   [Attempt 14] Error listing pods: Get "https://xxxxxxxxxxxxxxx/api/v1/namespaces/openshift-kube-apiserver/pods?labelSelector=app%3Dopenshift-kube-apiserver%2Capiserver%3Dtrue": net/http: TLS handshake timeout @ 10/17/25 12:27:45.257
    STEP:   [Attempt 15] Error listing pods: Get "https://xxxxxxxxxxxxxxx/api/v1/namespaces/openshift-kube-apiserver/pods?labelSelector=app%3Dopenshift-kube-apiserver%2Capiserver%3Dtrue": net/http: TLS handshake timeout @ 10/17/25 12:27:55.604
    STEP:   [Attempt 16] All 3 pods are running. New pods: 1/1. Revision: 12 @ 10/17/25 12:27:57.595
    STEP:   [Attempt 20] All 3 pods are running. New pods: 2/2. Revision: 12 @ 10/17/25 12:28:54.617
    STEP:   [Attempt 24] All 3 pods are running. New pods: 2/2. Revision: 12 @ 10/17/25 12:29:54.655
    STEP:   [Attempt 28] All 3 pods are running. New pods: 2/2. Revision: 12 @ 10/17/25 12:30:54.555
    STEP: [OK] New revision rolled out successfully in 8m30.982850458s @ 10/17/25 12:31:09.516
    STEP:   Configuration took: 8m31.541280917s total @ 10/17/25 12:31:09.516
    STEP: Step 4: Verifying event-ttl=5m in kube-apiserver config @ 10/17/25 12:31:09.516
    STEP: Checking event-ttl in pod config file @ 10/17/25 12:31:09.516
    STEP:   Checking config file in pod: kube-apiserver-ci-ln-7vzxymt-72292-z8z4t-master-0 @ 10/17/25 12:31:10.141
    STEP:   [OK] Found event-ttl=5m in config.yaml @ 10/17/25 12:31:10.536
    STEP: [OK] eventTTLMinutes=5 verified in config file @ 10/17/25 12:31:10.536
    STEP: 
    Step 5: Validating that events actually expire after 5 minutes @ 10/17/25 12:31:10.536
    STEP:   Waiting 30 seconds for new TTL configuration to fully propagate... @ 10/17/25 12:31:10.536
    STEP: Creating test namespace: event-ttl-test-5m-1760684500 @ 10/17/25 12:31:40.537
    STEP: [OK] Test namespace created: event-ttl-test-5m-1760684500 @ 10/17/25 12:31:40.897
    STEP: Creating NEW test event: apiload-event-1760684500 in namespace: event-ttl-test-5m-1760684500 @ 10/17/25 12:31:40.897
    STEP:   (This event should expire after the configured TTL, not the default 3h) @ 10/17/25 12:31:40.897
    STEP:   Event creation time: 2025-10-17T12:31:40+05:30 @ 10/17/25 12:31:40.897
    STEP: [OK] Event created at: 2025-10-17T12:31:41+05:30 @ 10/17/25 12:31:41.251
    STEP: [OK] Event confirmed to exist (UID: 285cf1d4-80d8-4ea4-bde3-614e7d13e93f) @ 10/17/25 12:31:41.598
    STEP:   Event CreationTimestamp: 2025-10-17T12:31:41+05:30 @ 10/17/25 12:31:41.598
    STEP:   This NEW event should expire after 5m (not the default 3h) @ 10/17/25 12:31:41.598
    STEP: Waiting 8 minutes for event to expire (expected expiration: 2025-10-17T12:39:41+05:30)... @ 10/17/25 12:31:41.598
    STEP:   ... 1/8 minutes elapsed @ 10/17/25 12:32:41.6
    STEP:   ... 2/8 minutes elapsed @ 10/17/25 12:33:41.599
    STEP:   ... 3/8 minutes elapsed @ 10/17/25 12:34:41.599
    STEP:   ... 4/8 minutes elapsed @ 10/17/25 12:35:41.598
    STEP:   ... 5/8 minutes elapsed @ 10/17/25 12:36:41.599
    STEP:   ... 6/8 minutes elapsed @ 10/17/25 12:37:41.6
    STEP:   ... 7/8 minutes elapsed @ 10/17/25 12:38:41.608
    STEP:   ... 8/8 minutes elapsed @ 10/17/25 12:39:41.608
    STEP: [OK] Event expired and deleted after approximately 8m0s @ 10/17/25 12:39:47.062
    STEP:   Expected TTL: 5m, Actual TTL: 8m0s @ 10/17/25 12:39:47.062
    STEP: 
    [SUCCESS] All steps completed successfully for eventTTLMinutes=5 @ 10/17/25 12:39:47.062
    STEP:   Total test duration: 17m10.20367875s @ 10/17/25 12:39:47.062
    STEP: Cleaning up test namespace: event-ttl-test-5m-1760684500 @ 10/17/25 12:39:47.062
    STEP: 
    Cleaning up eventTTLMinutes=5 configuration @ 10/17/25 12:39:47.722
    STEP:   [OK] eventTTLMinutes configuration removed @ 10/17/25 12:39:48.19
    STEP:   Waiting for kube-apiserver rollout after cleanup... @ 10/17/25 12:39:48.191
    STEP:   Initial state: 3 pods, oldest created at 2025-10-17T12:25:21+05:30, initial revision: 12 @ 10/17/25 12:39:48.663
    STEP:   [Attempt 1] All 3 pods are running. New pods: 0/0. Revision: 12 @ 10/17/25 12:40:04.151
    STEP:   [Attempt 4] All 3 pods are running. New pods: 0/0. Revision: 12 @ 10/17/25 12:40:49.265
    STEP:   [Attempt 8] All 3 pods are running. New pods: 0/0. Revision: 12 @ 10/17/25 12:41:49.201
    STEP:   [Attempt 12] All 3 pods are running. New pods: 1/1. Revision: 13 @ 10/17/25 12:42:49.227
    STEP:   [Attempt 16] All 3 pods are running. New pods: 1/1. Revision: 13 @ 10/17/25 12:43:49.236
    STEP:   [Attempt 20] All 3 pods are running. New pods: 1/1. Revision: 13 @ 10/17/25 12:44:49.128
    STEP:   [Attempt 23] 2/3 pods running. Not running: [kube-apiserver-ci-ln-7vzxymt-72292-z8z4t-master-1 (Pending)]. New pods: 1/2 running @ 10/17/25 12:45:34.148
    STEP:   [Attempt 24] All 3 pods are running. New pods: 2/2. Revision: 13 @ 10/17/25 12:45:49.637
    STEP:   [Attempt 28] All 3 pods are running. New pods: 2/2. Revision: 13 @ 10/17/25 12:46:51.06
    STEP:   [Attempt 32] All 3 pods are running. New pods: 2/2. Revision: 13 @ 10/17/25 12:47:49.082
    STEP:   [OK] Kube-apiserver rollout completed after cleanup @ 10/17/25 12:48:34.166
  • [1557.313 seconds]
  ------------------------------

  Ran 1 of 1 Specs in 1557.315 seconds
  SUCCESS! -- 1 Passed | 0 Failed | 0 Pending | 0 Skipped
[
  {
    "name": "[Jira:kube-apiserver][sig-api-machinery][FeatureGate:EventTTL] Event TTL Configuration should configure and validate eventTTLMinutes=5m [Serial][Disruptive][Slow][Suite:openshift/cluster-kube-apiserver-operator/conformance/serial]",
    "lifecycle": "blocking",
    "duration": 1557319,
    "startTime": "2025-10-17 06:52:36.847517 UTC",
    "endTime": "2025-10-17 07:18:34.167477 UTC",
    "result": "passed",
    "output": "  STEP: === Starting test for eventTTLMinutes=5 at 2025-10-17T12:22:36+05:30 === @ 10/17/25 12:22:36.853\n  STEP: Step 1: Checking if EventTTL feature gate is present @ 10/17/25 12:22:36.853\n  STEP:   Current FeatureSet: TechPreviewNoUpgrade @ 10/17/25 12:22:37.976\n  STEP: [OK] EventTTL feature gate found and is already enabled @ 10/17/25 12:22:37.976\n  STEP: \n  Step 2: Configuring eventTTLMinutes=5 in KubeAPIServer CR @ 10/17/25 12:22:37.976\n  STEP:   Patch data: {\"spec\":{\"eventTTLMinutes\":5}} @ 10/17/25 12:22:37.976\n  STEP: [OK] eventTTLMinutes=5 configured at 2025-10-17T12:22:38+05:30 @ 10/17/25 12:22:38.534\n  STEP: Step 3: Waiting for new revision to roll out (timeout: 20 minutes)... @ 10/17/25 12:22:38.534\n  STEP:   Rollout started at: 2025-10-17T12:22:38+05:30 @ 10/17/25 12:22:38.534\n  STEP:   Initial state: 3 pods, oldest created at 2025-10-17T11:24:09+05:30, initial revision: 11 @ 10/17/25 12:22:39\n  STEP:   [Attempt 1] All 3 pods are running. New pods: 0/0. Revision: 11 @ 10/17/25 12:22:54.447\n  STEP:   [Attempt 4] All 3 pods are running. New pods: 0/0. Revision: 11 @ 10/17/25 12:23:52.5\n  STEP:   [Attempt 8] Error listing pods: Get \"https://xxxxxxxxxxxxxxx/api/v1/namespaces/openshift-kube-apiserver/pods?labelSelector=app%3Dopenshift-kube-apiserver%2Capiserver%3Dtrue\": dial tcp: lookup xxxxxxxxxxxxxxx: i/o timeout - error from a previous attempt: http2: client connection lost @ 10/17/25 12:25:36.037\n  STEP:   [Attempt 9] Error listing pods: Get \"https://xxxxxxxxxxxxxxx/api/v1/namespaces/openshift-kube-apiserver/pods?labelSelector=app%3Dopenshift-kube-apiserver%2Capiserver%3Dtrue\": dial tcp: lookup xxxxxxxxxxxxxxx: i/o timeout @ 10/17/25 12:26:06.039\n  STEP:   [Attempt 10] Error listing pods: Get \"https://xxxxxxxxxxxxxxx/api/v1/namespaces/openshift-kube-apiserver/pods?labelSelector=app%3Dopenshift-kube-apiserver%2Capiserver%3Dtrue\": net/http: TLS handshake timeout @ 10/17/25 12:26:44.198\n  STEP:   [Attempt 11] Error listing pods: Get \"https://xxxxxxxxxxxxxxx/api/v1/namespaces/openshift-kube-apiserver/pods?labelSelector=app%3Dopenshift-kube-apiserver%2Capiserver%3Dtrue\": net/http: TLS handshake timeout @ 10/17/25 12:26:54.284\n  STEP:   [Attempt 12] Error listing pods: Get \"https://xxxxxxxxxxxxxxx/api/v1/namespaces/openshift-kube-apiserver/pods?labelSelector=app%3Dopenshift-kube-apiserver%2Capiserver%3Dtrue\": net/http: TLS handshake timeout @ 10/17/25 12:27:04.637\n  STEP:   [Attempt 13] Error listing pods: Get \"https://xxxxxxxxxxxxxxx/api/v1/namespaces/openshift-kube-apiserver/pods?labelSelector=app%3Dopenshift-kube-apiserver%2Capiserver%3Dtrue\": net/http: TLS handshake timeout @ 10/17/25 12:27:33.575\n  STEP:   [Attempt 14] Error listing pods: Get \"https://xxxxxxxxxxxxxxx/api/v1/namespaces/openshift-kube-apiserver/pods?labelSelector=app%3Dopenshift-kube-apiserver%2Capiserver%3Dtrue\": net/http: TLS handshake timeout @ 10/17/25 12:27:45.257\n  STEP:   [Attempt 15] Error listing pods: Get \"https://xxxxxxxxxxxxxxx/api/v1/namespaces/openshift-kube-apiserver/pods?labelSelector=app%3Dopenshift-kube-apiserver%2Capiserver%3Dtrue\": net/http: TLS handshake timeout @ 10/17/25 12:27:55.604\n  STEP:   [Attempt 16] All 3 pods are running. New pods: 1/1. Revision: 12 @ 10/17/25 12:27:57.595\n  STEP:   [Attempt 20] All 3 pods are running. New pods: 2/2. Revision: 12 @ 10/17/25 12:28:54.617\n  STEP:   [Attempt 24] All 3 pods are running. New pods: 2/2. Revision: 12 @ 10/17/25 12:29:54.655\n  STEP:   [Attempt 28] All 3 pods are running. New pods: 2/2. Revision: 12 @ 10/17/25 12:30:54.555\n  STEP: [OK] New revision rolled out successfully in 8m30.982850458s @ 10/17/25 12:31:09.516\n  STEP:   Configuration took: 8m31.541280917s total @ 10/17/25 12:31:09.516\n  STEP: Step 4: Verifying event-ttl=5m in kube-apiserver config @ 10/17/25 12:31:09.516\n  STEP: Checking event-ttl in pod config file @ 10/17/25 12:31:09.516\n  STEP:   Checking config file in pod: kube-apiserver-ci-ln-7vzxymt-72292-z8z4t-master-0 @ 10/17/25 12:31:10.141\n  STEP:   [OK] Found event-ttl=5m in config.yaml @ 10/17/25 12:31:10.536\n  STEP: [OK] eventTTLMinutes=5 verified in config file @ 10/17/25 12:31:10.536\n  STEP: \n  Step 5: Validating that events actually expire after 5 minutes @ 10/17/25 12:31:10.536\n  STEP:   Waiting 30 seconds for new TTL configuration to fully propagate... @ 10/17/25 12:31:10.536\n  STEP: Creating test namespace: event-ttl-test-5m-1760684500 @ 10/17/25 12:31:40.537\n  STEP: [OK] Test namespace created: event-ttl-test-5m-1760684500 @ 10/17/25 12:31:40.897\n  STEP: Creating NEW test event: apiload-event-1760684500 in namespace: event-ttl-test-5m-1760684500 @ 10/17/25 12:31:40.897\n  STEP:   (This event should expire after the configured TTL, not the default 3h) @ 10/17/25 12:31:40.897\n  STEP:   Event creation time: 2025-10-17T12:31:40+05:30 @ 10/17/25 12:31:40.897\n  STEP: [OK] Event created at: 2025-10-17T12:31:41+05:30 @ 10/17/25 12:31:41.251\n  STEP: [OK] Event confirmed to exist (UID: 285cf1d4-80d8-4ea4-bde3-614e7d13e93f) @ 10/17/25 12:31:41.598\n  STEP:   Event CreationTimestamp: 2025-10-17T12:31:41+05:30 @ 10/17/25 12:31:41.598\n  STEP:   This NEW event should expire after 5m (not the default 3h) @ 10/17/25 12:31:41.598\n  STEP: Waiting 8 minutes for event to expire (expected expiration: 2025-10-17T12:39:41+05:30)... @ 10/17/25 12:31:41.598\n  STEP:   ... 1/8 minutes elapsed @ 10/17/25 12:32:41.6\n  STEP:   ... 2/8 minutes elapsed @ 10/17/25 12:33:41.599\n  STEP:   ... 3/8 minutes elapsed @ 10/17/25 12:34:41.599\n  STEP:   ... 4/8 minutes elapsed @ 10/17/25 12:35:41.598\n  STEP:   ... 5/8 minutes elapsed @ 10/17/25 12:36:41.599\n  STEP:   ... 6/8 minutes elapsed @ 10/17/25 12:37:41.6\n  STEP:   ... 7/8 minutes elapsed @ 10/17/25 12:38:41.608\n  STEP:   ... 8/8 minutes elapsed @ 10/17/25 12:39:41.608\n  STEP: [OK] Event expired and deleted after approximately 8m0s @ 10/17/25 12:39:47.062\n  STEP:   Expected TTL: 5m, Actual TTL: 8m0s @ 10/17/25 12:39:47.062\n  STEP: \n  [SUCCESS] All steps completed successfully for eventTTLMinutes=5 @ 10/17/25 12:39:47.062\n  STEP:   Total test duration: 17m10.20367875s @ 10/17/25 12:39:47.062\n  STEP: Cleaning up test namespace: event-ttl-test-5m-1760684500 @ 10/17/25 12:39:47.062\n  STEP: \n  Cleaning up eventTTLMinutes=5 configuration @ 10/17/25 12:39:47.722\n  STEP:   [OK] eventTTLMinutes configuration removed @ 10/17/25 12:39:48.19\n  STEP:   Waiting for kube-apiserver rollout after cleanup... @ 10/17/25 12:39:48.191\n  STEP:   Initial state: 3 pods, oldest created at 2025-10-17T12:25:21+05:30, initial revision: 12 @ 10/17/25 12:39:48.663\n  STEP:   [Attempt 1] All 3 pods are running. New pods: 0/0. Revision: 12 @ 10/17/25 12:40:04.151\n  STEP:   [Attempt 4] All 3 pods are running. New pods: 0/0. Revision: 12 @ 10/17/25 12:40:49.265\n  STEP:   [Attempt 8] All 3 pods are running. New pods: 0/0. Revision: 12 @ 10/17/25 12:41:49.201\n  STEP:   [Attempt 12] All 3 pods are running. New pods: 1/1. Revision: 13 @ 10/17/25 12:42:49.227\n  STEP:   [Attempt 16] All 3 pods are running. New pods: 1/1. Revision: 13 @ 10/17/25 12:43:49.236\n  STEP:   [Attempt 20] All 3 pods are running. New pods: 1/1. Revision: 13 @ 10/17/25 12:44:49.128\n  STEP:   [Attempt 23] 2/3 pods running. Not running: [kube-apiserver-ci-ln-7vzxymt-72292-z8z4t-master-1 (Pending)]. New pods: 1/2 running @ 10/17/25 12:45:34.148\n  STEP:   [Attempt 24] All 3 pods are running. New pods: 2/2. Revision: 13 @ 10/17/25 12:45:49.637\n  STEP:   [Attempt 28] All 3 pods are running. New pods: 2/2. Revision: 13 @ 10/17/25 12:46:51.06\n  STEP:   [Attempt 32] All 3 pods are running. New pods: 2/2. Revision: 13 @ 10/17/25 12:47:49.082\n  STEP:   [OK] Kube-apiserver rollout completed after cleanup @ 10/17/25 12:48:34.166\n"
  }
]%                               

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Oct 16, 2025

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci openshift-ci bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Oct 16, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 16, 2025

Warning

Rate limit exceeded

@gangwgr has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 5 minutes and 54 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 70bf2be and 9752fbd.

📒 Files selected for processing (7)
  • .gitignore (1 hunks)
  • Dockerfile.rhel7 (2 hunks)
  • Makefile (2 hunks)
  • test/OWNERS (1 hunks)
  • test/extended/tests-extension/.openshift-tests-extension/openshift_payload_cluster-kube-apiserve-operator.json (1 hunks)
  • test/extended/tests-extension/Makefile (1 hunks)
  • test/extended/tests-extension/README.md (5 hunks)

Walkthrough

Moves the test extension into test/extended/tests-extension as a separate Go module, updates build/packaging paths and scripts (.gitignore, Makefile, Dockerfile.rhel7, root go.mod), adds module files (go.mod, cmd/main.go, test/OWNERS, README), and introduces a new EventTTL extended test implementation.

Changes

Cohort / File(s) Change Summary
Build config & packaging
\.gitignore, Dockerfile.rhel7, Makefile
Updated ignored path to test/extended/tests-extension/.... Adjusted Dockerfile gzip/copy steps to source artifacts from test/extended/tests-extension/.... Added TESTS_EXT_DIR and changed tests-ext-build/tests-ext-update to run inside test/extended/tests-extension.
Root module
go.mod
Removed direct requires for github.com/onsi/ginkgo/v2, github.com/onsi/gomega, and github.com/openshift-eng/openshift-tests-extension; removed an indirect github.com/go-task/slim-sprig/v3; removed a replace directive and performed minor formatting cleanup.
Tests-extension module files
test/extended/tests-extension/go.mod, test/extended/tests-extension/cmd/main.go
Added new module test/extended/tests-extension with pinned requires and replace directives. Changed side-effect import in cmd/main.go to register tests from the new module path.
New extended test & helpers
test/extended/tests-extension/event_ttl.go
Added EventTTL extended test: toggles feature gate if needed, patches KubeAPIServer CR with event TTLs, waits for API server rollout, verifies kube-apiserver configmap, creates an Event and asserts expiration, and includes helper polling/rollout/verification functions and cleanup.
Documentation & metadata
test/extended/tests-extension/README.md, test/OWNERS
README examples updated to use explicit test/extended/tests-extension/cluster-kube-apiserver-operator-tests-ext invocations. Added test/OWNERS listing reviewers/approvers for the kube-apiserver tests.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "Adding test cases for 'Add support for event-ttl in Kube API Server Operator'" is directly related to the primary objective of the changeset. The title clearly and accurately describes the main goal: introducing test cases for the event-ttl feature. While the changeset also includes significant supporting changes such as creating a separate Go module for the test extension, restructuring the test directory, and updating build infrastructure, these are secondary infrastructure changes that enable the primary objective of adding tests. The title is specific, concise, and free from vague terminology or misleading content.
Description Check ✅ Passed The pull request description is clearly related to the changeset. It explicitly lists all major changes being made: adding test cases for event-ttl, creating a separate Go module for the test extension, moving test code and metadata to the tests-extension directory, updating build infrastructure, and removing test dependencies from the root go.mod. Additionally, the description includes detailed evidence of a successful test execution with step-by-step output showing the event-ttl feature validation. The description is neither vague nor generic—it provides specific, actionable information about what is being changed and why.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Oct 16, 2025

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: gangwgr
Once this PR has been reviewed and has the lgtm label, please assign vrutkovs for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

gangwgr

This comment was marked as duplicate.

gangwgr

This comment was marked as duplicate.

gangwgr

This comment was marked as duplicate.

gangwgr

This comment was marked as duplicate.

Copy link
Author

@gangwgr gangwgr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Code review complete. Found 13 suggestion(s).

@gangwgr gangwgr force-pushed the event-ttl-tests branch 5 times, most recently from 52b8f7e to a5e1528 Compare October 17, 2025 07:28
@gangwgr gangwgr changed the title Pre-merge testing Adding test cases for "Add support for event-ttl in Kube API Server Operator" Oct 17, 2025
@gangwgr gangwgr marked this pull request as ready for review October 22, 2025 11:36
@openshift-ci openshift-ci bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Oct 22, 2025
@openshift-ci openshift-ci bot requested review from dgrisonnet and tkashem October 22, 2025 11:37
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (6)
test/extended/event_ttl.go (6)

46-53: Cap test duration; make TTLs configurable

Three serial subtests (5/10/15) + rollouts can exceed CI time. Prefer a single default TTL (e.g., 5) with env override.

- // Loop to create separate test cases for each TTL value (5m, 10m, 15m)
- testValues := []int32{5, 10, 15}
+ // Default TTLs; allow override via EVENT_TTL_MINUTES="5,10,15"
+ testValues := []int32{5}
+ if v := strings.TrimSpace(getenvDefault("EVENT_TTL_MINUTES", "")); v != "" {
+   var vals []int32
+   for _, s := range strings.Split(v, ",") {
+     if m, err := strconv.Atoi(strings.TrimSpace(s)); err == nil && m > 0 {
+       vals = append(vals, int32(m))
+     }
+   }
+   if len(vals) > 0 {
+     testValues = vals
+   }
+ }

Add helpers near imports:

+func getenvDefault(k, d string) string { if v := os.Getenv(k); v != "" { return v }; return d }

262-296: Poll for deletion to finish sooner; avoid fixed sleep

Sleeping for TTL+buffer guarantees maximum wait time even if the event expires earlier. Polling shortens test runtime and reduces flake due to clock skew.

- bufferMinutes := int32(3) // Increased buffer from 2 to 3 minutes
- waitDuration := time.Duration(ttl+bufferMinutes) * time.Minute
- expectedExpirationTime := creationTime.Add(waitDuration)
+ bufferMinutes := int32(3) // buffer for GC intervals/clock skew
+ maxWait := time.Duration(ttl+bufferMinutes) * time.Minute
+ expectedExpirationTime := creationTime.Add(maxWait)
@@
- // Log progress every minute
- ticker := time.NewTicker(1 * time.Minute)
- done := make(chan bool, 1) // Buffered channel to prevent goroutine leak
- elapsed := 0
+ // Log progress every minute while polling
+ ticker := time.NewTicker(1 * time.Minute)
+ done := make(chan struct{})
+ elapsed := 0
@@
- case done <- true:
+ case <-done:
@@
-go func() {
+go func() {
     for {
         select {
-        case <-done:
+        case <-done:
             return
         case <-ticker.C:
             elapsed++
             g.By(fmt.Sprintf("  ... %d/%d minutes elapsed", elapsed, int(ttl+bufferMinutes)))
         }
     }
 }()
 
-time.Sleep(waitDuration)
+// Poll until deleted or timeout
+_ = wait.PollUntilContextTimeout(ctx, 10*time.Second, maxWait, false, func(_ context.Context) (bool, error) {
+    _, err := kubeClient.CoreV1().Events(testNamespace).Get(ctx, eventName, metav1.GetOptions{})
+    if err != nil && apierrors.IsNotFound(err) {
+        return true, nil
+    }
+    return false, nil
+})
+close(done)
+ticker.Stop()

445-517: Parse config.yaml instead of string contains; accept “5m0s” variants

String search is fragile. Parse YAML and validate apiServerArguments.event-ttl, tolerating duration formats like 5m0s.

@@
- configData, found := configMap.Data["config.yaml"]
+ configData, found := configMap.Data["config.yaml"]
  o.Expect(found).To(o.BeTrue(), "config.yaml not found in ConfigMap")
@@
- // Parse the config and look for event-ttl in apiServerArguments
- expectedTTL := fmt.Sprintf("%dm", expectedMinutes)
-
- // Check if event-ttl is in the config
- hasEventTTL := strings.Contains(configData, "event-ttl")
- if !hasEventTTL {
-   // event-ttl must be present in the config
-   o.Expect(hasEventTTL).To(o.BeTrue(),
-       fmt.Sprintf("event-ttl not found in ConfigMap %s (path in pod: %s)", configMapName, configPath))
- }
-
- // Further verify it has the correct value
- hasCorrectValue := strings.Contains(configData, fmt.Sprintf("- %s", expectedTTL)) ||
-     strings.Contains(configData, fmt.Sprintf("\"%s\"", expectedTTL)) ||
-     strings.Contains(configData, fmt.Sprintf("'%s'", expectedTTL))
+ // Parse the config and look for event-ttl in apiServerArguments
+ type kasConfig struct {
+   APIServerArguments map[string][]string `json:"apiServerArguments" yaml:"apiServerArguments"`
+ }
+ var cfg kasConfig
+ o.Expect(yaml.Unmarshal([]byte(configData), &cfg)).NotTo(o.HaveOccurred(), "failed to unmarshal config.yaml")
+ vals, ok := cfg.APIServerArguments["event-ttl"]
+ o.Expect(ok && len(vals) > 0).To(o.BeTrue(), "event-ttl not present in apiServerArguments")
+ // Accept "5m" or "5m0s"
+ expectedTTL := fmt.Sprintf("%dm", expectedMinutes)
+ hasCorrectValue := false
+ for _, v := range vals {
+   if v == expectedTTL {
+     hasCorrectValue = true
+     break
+   }
+   if d, err := time.ParseDuration(v); err == nil {
+     if d == time.Duration(expectedMinutes)*time.Minute {
+       hasCorrectValue = true
+       break
+     }
+   }
+ }
@@
- if hasCorrectValue {
+ if hasCorrectValue {
     g.By(fmt.Sprintf("  [OK] Found event-ttl=%s in config.yaml", expectedTTL))
 } else {
@@
-   o.Expect(hasCorrectValue).To(o.BeTrue(),
-       fmt.Sprintf("event-ttl found in config but value doesn't match expected %s", expectedTTL))
+   o.Expect(hasCorrectValue).To(o.BeTrue(),
+       fmt.Sprintf("event-ttl value doesn't match expected %s", expectedTTL))
 }

Add import:

+ "sigs.k8s.io/yaml"

226-244: Event object: prefer events.k8s.io/v1 or drop deprecated fields

corev1.Event with FirstTimestamp/LastTimestamp is deprecated. Either use events.k8s.io/v1.Event or omit deprecated fields; only CreationTimestamp is needed for TTL.

- FirstTimestamp: metav1.Now(),
- LastTimestamp:  metav1.Now(),
- Count:          1,
+ // Omit deprecated FirstTimestamp/LastTimestamp; TTL uses object lease
+ Count:          1,

Optional switch to events.k8s.io/v1 if the repo already uses it elsewhere.


183-187: Make verify helper return an error instead of hard-failing

Returning error allows richer context at call site and keeps helpers reusable.

-func verifyEventTTLInConfigFile(ctx context.Context, kubeClient *kubernetes.Clientset, expectedMinutes int32) {
+func verifyEventTTLInConfigFile(ctx context.Context, kubeClient *kubernetes.Clientset, expectedMinutes int32) error {
@@
- o.Expect(err).NotTo(o.HaveOccurred())
+ if err != nil { return err }
@@
- o.Expect(targetPod).NotTo(o.BeNil(), "no running kube-apiserver pod found")
+ if targetPod == nil { return fmt.Errorf("no running kube-apiserver pod found") }
@@
- o.Expect(err).NotTo(o.HaveOccurred(), "failed to get config ConfigMap")
+ if err != nil { return fmt.Errorf("failed to get config ConfigMap: %w", err) }
@@
- o.Expect(found).To(o.BeTrue(), "config.yaml not found in ConfigMap")
+ if !found { return fmt.Errorf("config.yaml not found in ConfigMap") }
@@
- o.Expect(hasCorrectValue).To(o.BeTrue(),
-   fmt.Sprintf("event-ttl value doesn't match expected %s", expectedTTL))
+ if !hasCorrectValue {
+   return fmt.Errorf("event-ttl value doesn't match expected %s", expectedTTL)
+ }
+ return nil

And at call site:

- verifyEventTTLInConfigFile(ctx, kubeClient, ttl)
+ o.Expect(verifyEventTTLInConfigFile(ctx, kubeClient, ttl)).NotTo(o.HaveOccurred())

31-44: Prefer context.WithTimeout for one-shot API calls

Using context.TODO() can hang on transient apiserver issues. Wrap one-shot calls in short timeouts; keep long waits in your explicit waiters.

- ctx = context.TODO()
+ var cancel context.CancelFunc
+ ctx, cancel = context.WithCancel(context.Background())
+ g.DeferCleanup(cancel)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between adc82f6 and 0abb026.

📒 Files selected for processing (1)
  • test/extended/event_ttl.go (1 hunks)
🔇 Additional comments (1)
test/extended/event_ttl.go (1)

23-44: Solid test harness setup

Client wiring and BeforeEach look good for extended tests.

Comment on lines +297 to +302
// Verify event is deleted
actualExpirationTime := time.Now()
_, err = kubeClient.CoreV1().Events(testNamespace).Get(ctx, eventName, metav1.GetOptions{})
o.Expect(err).To(o.HaveOccurred(), "event should be deleted after TTL")
o.Expect(err.Error()).To(o.ContainSubstring("not found"), "event should return 'not found' error")

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use apierrors.IsNotFound instead of string matching

String matching “not found” is brittle and locale-dependent. Use apierrors.IsNotFound.

@@
-import (
+import (
@@
     metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+    apierrors "k8s.io/apimachinery/pkg/api/errors"
@@
- o.Expect(err).To(o.HaveOccurred(), "event should be deleted after TTL")
- o.Expect(err.Error()).To(o.ContainSubstring("not found"), "event should return 'not found' error")
+ o.Expect(err).To(o.HaveOccurred(), "event should be deleted after TTL")
+ o.Expect(apierrors.IsNotFound(err)).To(o.BeTrue(), "expected NotFound error after TTL expiration")

Also applies to: 3-21

Comment on lines +344 to +443
func waitForAPIServerRollout(ctx context.Context, kubeClient *kubernetes.Clientset, timeout time.Duration) error {
// First, get the current revision and pod creation times BEFORE we start waiting
initialPods, err := kubeClient.CoreV1().Pods(operatorclient.TargetNamespace).List(ctx, metav1.ListOptions{
LabelSelector: "app=openshift-kube-apiserver,apiserver=true",
})
if err != nil {
g.By(fmt.Sprintf(" Warning: Could not get initial pods: %v", err))
}

// Track the oldest pod creation time - we need to see pods newer than this
var oldestPodTime time.Time
initialRevision := ""
if initialPods != nil && len(initialPods.Items) > 0 {
oldestPodTime = initialPods.Items[0].CreationTimestamp.Time
for _, pod := range initialPods.Items {
if pod.CreationTimestamp.Time.Before(oldestPodTime) {
oldestPodTime = pod.CreationTimestamp.Time
}
// Get the revision from labels
if rev, ok := pod.Labels["revision"]; ok && initialRevision == "" {
initialRevision = rev
}
}
g.By(fmt.Sprintf(" Initial state: %d pods, oldest created at %s, initial revision: %s",
len(initialPods.Items), oldestPodTime.Format(time.RFC3339), initialRevision))
}

attempt := 0
lastPodCount := 0
lastNotRunningCount := 0
rolloutStartTime := time.Now()

return wait.PollUntilContextTimeout(ctx, 15*time.Second, timeout, false, func(cxt context.Context) (bool, error) {
attempt++
pods, err := kubeClient.CoreV1().Pods(operatorclient.TargetNamespace).List(cxt, metav1.ListOptions{
LabelSelector: "app=openshift-kube-apiserver,apiserver=true",
})
if err != nil {
g.By(fmt.Sprintf(" [Attempt %d] Error listing pods: %v", attempt, err))
return false, nil
}

if len(pods.Items) == 0 {
g.By(fmt.Sprintf(" [Attempt %d] No kube-apiserver pods found yet", attempt))
return false, nil
}

// Count pods and check if we have new pods (created after rollout started)
notRunningCount := 0
newPodsCount := 0
runningNewPodsCount := 0
var notRunningPods []string
var currentRevision string

for _, pod := range pods.Items {
// Check if this is a new pod (created after we started waiting for rollout)
isNewPod := pod.CreationTimestamp.Time.After(rolloutStartTime)

if pod.Status.Phase != corev1.PodRunning {
notRunningCount++
notRunningPods = append(notRunningPods, fmt.Sprintf("%s (%s)", pod.Name, pod.Status.Phase))
}

if isNewPod {
newPodsCount++
if pod.Status.Phase == corev1.PodRunning {
runningNewPodsCount++
}
}

// Track current revision
if rev, ok := pod.Labels["revision"]; ok && currentRevision == "" {
currentRevision = rev
}
}

// We need ALL pods to be:
// 1. Running
// 2. Created after rollout started (new pods with new configuration)
// We expect 3 pods for a typical control plane
expectedPodCount := 3
allPodsNewAndRunning := (newPodsCount == expectedPodCount && runningNewPodsCount == expectedPodCount)

// Log only when state changes or every 4th attempt (1 minute)
if notRunningCount != lastNotRunningCount || len(pods.Items) != lastPodCount || attempt%4 == 0 {
if notRunningCount > 0 {
g.By(fmt.Sprintf(" [Attempt %d] %d/%d pods running. Not running: %v. New pods: %d/%d running",
attempt, len(pods.Items)-notRunningCount, len(pods.Items), notRunningPods, runningNewPodsCount, newPodsCount))
} else {
g.By(fmt.Sprintf(" [Attempt %d] All %d pods are running. New pods: %d/%d. Revision: %s",
attempt, len(pods.Items), runningNewPodsCount, newPodsCount, currentRevision))
}
lastPodCount = len(pods.Items)
lastNotRunningCount = notRunningCount
}

// Success: all expected pods are new and running
return allPodsNewAndRunning, nil
})
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Rollout waiter: don’t assume 3 pods; detect revision change

Hardcoding expectedPodCount=3 breaks on SNO and other topologies. Also, using “created after rolloutStartTime” is flaky. Compute expected count from current pods and gate success on all pods Running and sharing a single revision different from the initial one.

@@
-    // We expect 3 pods for a typical control plane
-    expectedPodCount := 3
-    allPodsNewAndRunning := (newPodsCount == expectedPodCount && runningNewPodsCount == expectedPodCount)
+    // Determine expected pods dynamically
+    expectedPodCount := len(pods.Items)
+    // All pods running and on the same (new) revision
+    sameRevision := true
+    rev0 := ""
+    for _, p := range pods.Items {
+        if r, ok := p.Labels["revision"]; ok {
+            if rev0 == "" { rev0 = r } else if rev0 != r { sameRevision = false }
+        } else {
+            sameRevision = false
+        }
+    }
+    // If we knew the initial revision, consider success only if it changed
+    revChanged := (initialRevision == "") || (rev0 != "" && rev0 != initialRevision)
+    allRunning := (notRunningCount == 0)
+    allPodsNewAndRunning := allRunning && sameRevision && revChanged && (expectedPodCount > 0)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func waitForAPIServerRollout(ctx context.Context, kubeClient *kubernetes.Clientset, timeout time.Duration) error {
// First, get the current revision and pod creation times BEFORE we start waiting
initialPods, err := kubeClient.CoreV1().Pods(operatorclient.TargetNamespace).List(ctx, metav1.ListOptions{
LabelSelector: "app=openshift-kube-apiserver,apiserver=true",
})
if err != nil {
g.By(fmt.Sprintf(" Warning: Could not get initial pods: %v", err))
}
// Track the oldest pod creation time - we need to see pods newer than this
var oldestPodTime time.Time
initialRevision := ""
if initialPods != nil && len(initialPods.Items) > 0 {
oldestPodTime = initialPods.Items[0].CreationTimestamp.Time
for _, pod := range initialPods.Items {
if pod.CreationTimestamp.Time.Before(oldestPodTime) {
oldestPodTime = pod.CreationTimestamp.Time
}
// Get the revision from labels
if rev, ok := pod.Labels["revision"]; ok && initialRevision == "" {
initialRevision = rev
}
}
g.By(fmt.Sprintf(" Initial state: %d pods, oldest created at %s, initial revision: %s",
len(initialPods.Items), oldestPodTime.Format(time.RFC3339), initialRevision))
}
attempt := 0
lastPodCount := 0
lastNotRunningCount := 0
rolloutStartTime := time.Now()
return wait.PollUntilContextTimeout(ctx, 15*time.Second, timeout, false, func(cxt context.Context) (bool, error) {
attempt++
pods, err := kubeClient.CoreV1().Pods(operatorclient.TargetNamespace).List(cxt, metav1.ListOptions{
LabelSelector: "app=openshift-kube-apiserver,apiserver=true",
})
if err != nil {
g.By(fmt.Sprintf(" [Attempt %d] Error listing pods: %v", attempt, err))
return false, nil
}
if len(pods.Items) == 0 {
g.By(fmt.Sprintf(" [Attempt %d] No kube-apiserver pods found yet", attempt))
return false, nil
}
// Count pods and check if we have new pods (created after rollout started)
notRunningCount := 0
newPodsCount := 0
runningNewPodsCount := 0
var notRunningPods []string
var currentRevision string
for _, pod := range pods.Items {
// Check if this is a new pod (created after we started waiting for rollout)
isNewPod := pod.CreationTimestamp.Time.After(rolloutStartTime)
if pod.Status.Phase != corev1.PodRunning {
notRunningCount++
notRunningPods = append(notRunningPods, fmt.Sprintf("%s (%s)", pod.Name, pod.Status.Phase))
}
if isNewPod {
newPodsCount++
if pod.Status.Phase == corev1.PodRunning {
runningNewPodsCount++
}
}
// Track current revision
if rev, ok := pod.Labels["revision"]; ok && currentRevision == "" {
currentRevision = rev
}
}
// We need ALL pods to be:
// 1. Running
// 2. Created after rollout started (new pods with new configuration)
// We expect 3 pods for a typical control plane
expectedPodCount := 3
allPodsNewAndRunning := (newPodsCount == expectedPodCount && runningNewPodsCount == expectedPodCount)
// Log only when state changes or every 4th attempt (1 minute)
if notRunningCount != lastNotRunningCount || len(pods.Items) != lastPodCount || attempt%4 == 0 {
if notRunningCount > 0 {
g.By(fmt.Sprintf(" [Attempt %d] %d/%d pods running. Not running: %v. New pods: %d/%d running",
attempt, len(pods.Items)-notRunningCount, len(pods.Items), notRunningPods, runningNewPodsCount, newPodsCount))
} else {
g.By(fmt.Sprintf(" [Attempt %d] All %d pods are running. New pods: %d/%d. Revision: %s",
attempt, len(pods.Items), runningNewPodsCount, newPodsCount, currentRevision))
}
lastPodCount = len(pods.Items)
lastNotRunningCount = notRunningCount
}
// Success: all expected pods are new and running
return allPodsNewAndRunning, nil
})
}
func waitForAPIServerRollout(ctx context.Context, kubeClient *kubernetes.Clientset, timeout time.Duration) error {
// First, get the current revision and pod creation times BEFORE we start waiting
initialPods, err := kubeClient.CoreV1().Pods(operatorclient.TargetNamespace).List(ctx, metav1.ListOptions{
LabelSelector: "app=openshift-kube-apiserver,apiserver=true",
})
if err != nil {
g.By(fmt.Sprintf(" Warning: Could not get initial pods: %v", err))
}
// Track the oldest pod creation time - we need to see pods newer than this
var oldestPodTime time.Time
initialRevision := ""
if initialPods != nil && len(initialPods.Items) > 0 {
oldestPodTime = initialPods.Items[0].CreationTimestamp.Time
for _, pod := range initialPods.Items {
if pod.CreationTimestamp.Time.Before(oldestPodTime) {
oldestPodTime = pod.CreationTimestamp.Time
}
// Get the revision from labels
if rev, ok := pod.Labels["revision"]; ok && initialRevision == "" {
initialRevision = rev
}
}
g.By(fmt.Sprintf(" Initial state: %d pods, oldest created at %s, initial revision: %s",
len(initialPods.Items), oldestPodTime.Format(time.RFC3339), initialRevision))
}
attempt := 0
lastPodCount := 0
lastNotRunningCount := 0
rolloutStartTime := time.Now()
return wait.PollUntilContextTimeout(ctx, 15*time.Second, timeout, false, func(cxt context.Context) (bool, error) {
attempt++
pods, err := kubeClient.CoreV1().Pods(operatorclient.TargetNamespace).List(cxt, metav1.ListOptions{
LabelSelector: "app=openshift-kube-apiserver,apiserver=true",
})
if err != nil {
g.By(fmt.Sprintf(" [Attempt %d] Error listing pods: %v", attempt, err))
return false, nil
}
if len(pods.Items) == 0 {
g.By(fmt.Sprintf(" [Attempt %d] No kube-apiserver pods found yet", attempt))
return false, nil
}
// Count pods and check if we have new pods (created after rollout started)
notRunningCount := 0
newPodsCount := 0
runningNewPodsCount := 0
var notRunningPods []string
var currentRevision string
for _, pod := range pods.Items {
// Check if this is a new pod (created after we started waiting for rollout)
isNewPod := pod.CreationTimestamp.Time.After(rolloutStartTime)
if pod.Status.Phase != corev1.PodRunning {
notRunningCount++
notRunningPods = append(notRunningPods, fmt.Sprintf("%s (%s)", pod.Name, pod.Status.Phase))
}
if isNewPod {
newPodsCount++
if pod.Status.Phase == corev1.PodRunning {
runningNewPodsCount++
}
}
// Track current revision
if rev, ok := pod.Labels["revision"]; ok && currentRevision == "" {
currentRevision = rev
}
}
// Determine expected pods dynamically
expectedPodCount := len(pods.Items)
// All pods running and on the same (new) revision
sameRevision := true
rev0 := ""
for _, p := range pods.Items {
if r, ok := p.Labels["revision"]; ok {
if rev0 == "" { rev0 = r } else if rev0 != r { sameRevision = false }
} else {
sameRevision = false
}
}
// If we knew the initial revision, consider success only if it changed
revChanged := (initialRevision == "") || (rev0 != "" && rev0 != initialRevision)
allRunning := (notRunningCount == 0)
allPodsNewAndRunning := allRunning && sameRevision && revChanged && (expectedPodCount > 0)
// Log only when state changes or every 4th attempt (1 minute)
if notRunningCount != lastNotRunningCount || len(pods.Items) != lastPodCount || attempt%4 == 0 {
if notRunningCount > 0 {
g.By(fmt.Sprintf(" [Attempt %d] %d/%d pods running. Not running: %v. New pods: %d/%d running",
attempt, len(pods.Items)-notRunningCount, len(pods.Items), notRunningPods, runningNewPodsCount, newPodsCount))
} else {
g.By(fmt.Sprintf(" [Attempt %d] All %d pods are running. New pods: %d/%d. Revision: %s",
attempt, len(pods.Items), runningNewPodsCount, newPodsCount, currentRevision))
}
lastPodCount = len(pods.Items)
lastNotRunningCount = notRunningCount
}
// Success: all expected pods are new and running
return allPodsNewAndRunning, nil
})
}
🤖 Prompt for AI Agents
In test/extended/event_ttl.go around lines 344-443, the waiter currently
hardcodes expectedPodCount=3 and treats "new" pods by creation time
(rolloutStartTime), which fails on single-node or other topologies and is flaky;
change the logic to derive expectedPodCount from the current pod list
(expectedPodCount := len(pods.Items)), stop using creation
timestamps/newPodsCount/runningNewPodsCount, and instead compute a single
currentRevision from pod labels and verify all pods share that same revision and
are in Phase Running and that currentRevision != initialRevision (or
initialRevision=="" treat as changed when any consistent revision appears). Gate
success on: all pods Running AND all pods have the same revision AND that
revision differs from the initialRevision observed before waiting; update
logging accordingly and remove the time-based "new pod" checks.

@openshift-merge-robot openshift-merge-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Oct 24, 2025
This commit introduces dependency isolation by creating a separate
Go module for the Cluster Kube API Server Operator test extension at
test/extended/tests-extension/.

Key benefits:
- Isolates test dependencies (ginkgo, gomega, openshift-tests-extension)
  from production operator code
- Prevents dependency version conflicts between operator and test framework
- Keeps production go.mod clean and focused on operator requirements
- Follows the pattern used by other OpenShift operators

The separate module includes:
- go.mod with test-specific dependencies
- go.sum with locked dependency versions
- Same k8s.io version pinning as root module via replace directives
- Local replace for main operator module to access test/library

Reference implementation:
https://github.com/openshift/operator-framework-operator-controller/tree/main/openshift/tests-extension
Relocates all test-related code into the isolated test module:

Test code moved:
- test/extended/event_ttl.go → test/extended/tests-extension/event_ttl.go
- test/extended/main.go → test/extended/tests-extension/main.go
- cmd/cluster-kube-apiserver-operator-tests-ext → test/extended/tests-extension/cmd/

Metadata moved:
- .openshift-tests-extension/ → test/extended/tests-extension/.openshift-tests-extension/

Import path updates:
- Updated cmd/main.go to import from tests-extension module

This consolidates all test extension components (code, binary entry point,
and test metadata) into a single self-contained directory that uses its
own go.mod for dependency management.

The test binary and metadata now live alongside the test code in
test/extended/tests-extension/, making the test module completely
independent from the operator codebase.
Updates Makefile, Dockerfile, and .gitignore to work with the new
test extension module location.

Makefile changes:
- Added TESTS_EXT_DIR variable pointing to test/extended/tests-extension
- tests-ext-build now builds from test/extended/tests-extension/
- tests-ext-update runs from and updates metadata in tests-extension dir
- Binary output: test/extended/tests-extension/cluster-kube-apiserver-operator-tests-ext

Dockerfile changes:
- Updated gzip path: test/extended/tests-extension/cluster-kube-apiserver-operator-tests-ext
- Updated COPY path for .gz file to match new location

.gitignore changes:
- Updated to ignore binary at new path: /test/extended/tests-extension/cluster-kube-apiserver-operator-tests-ext

The build process now:
1. cd into test/extended/tests-extension/ (where the separate go.mod is)
2. Build the binary using the isolated dependencies
3. Output binary stays within the test module directory
4. Docker build references the new paths for gzip and copy operations
Removes test-only dependencies from the production operator module
now that they are isolated in test/extended/tests-extension/go.mod.

Removed dependencies:
- github.com/onsi/ginkgo/v2 v2.22.0
- github.com/onsi/gomega v1.36.1
- github.com/openshift-eng/openshift-tests-extension v0.0.0-20250804142706-7b3ab438a292

Removed replace directive:
- github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2

Vendor directory updated:
- Removed test framework packages from vendor/
- Updated vendor/modules.txt to reflect clean dependencies

This achieves complete dependency isolation:
- Production go.mod is now free of test framework dependencies
- Test dependencies are managed separately in test/extended/tests-extension/go.mod
- No risk of version conflicts between operator and test framework
- Cleaner dependency tree for production builds

The operator binary no longer carries any testing framework overhead.
@openshift-merge-robot openshift-merge-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Oct 24, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/extended/tests-extension/cmd/main.go (1)

28-28: Fix identifier typo: use “cluster-kube-apiserver-operator” everywhere

The extension ID and suite names use “apiserve” (missing “r”). This breaks metadata file naming, suite discovery, and docs alignment.

Apply this diff:

- ext := e.NewExtension("openshift", "payload", "cluster-kube-apiserve-operator")
+ ext := e.NewExtension("openshift", "payload", "cluster-kube-apiserver-operator")
@@
- Name:    "openshift/cluster-kube-apiserve-operator/conformance/parallel",
+ Name:    "openshift/cluster-kube-apiserver-operator/conformance/parallel",
@@
- Name:    "openshift/cluster-kube-apiserve-operator/conformance/serial",
+ Name:    "openshift/cluster-kube-apiserver-operator/conformance/serial",
@@
- Name:    "openshift/cluster-kube-apiserve-operator/optional/slow",
+ Name:    "openshift/cluster-kube-apiserver-operator/optional/slow",
@@
- Name: "openshift/cluster-kube-apiserve-operator/all",
+ Name: "openshift/cluster-kube-apiserver-operator/all",

Also applies to: 32-33, 41-42, 50-51, 59-60

♻️ Duplicate comments (3)
test/extended/tests-extension/event_ttl.go (3)

23-29: Don’t overwrite cluster FeatureGate; merge-add EventTTL and restore original on cleanup
Overwrites spec to CustomNoUpgrade with only EventTTL and never restores. Preserve existing enabled/disabled entries, append EventTTL if missing, and restore the original spec in defer. This avoids collateral feature flips across the cluster.

Apply diffs:

@@
-        kubeClient     *kubernetes.Clientset
-        configClient   *configclient.Clientset
-        operatorClient *operatorclientset.Clientset
-        ctx            context.Context
+        kubeClient     *kubernetes.Clientset
+        configClient   *configclient.Clientset
+        operatorClient *operatorclientset.Clientset
+        ctx            context.Context
+        originalFeatureGateSpecJSON []byte
+        featureGatePatched bool
@@
-            defer func() {
+            defer func() {
                 g.By(fmt.Sprintf("\nCleaning up eventTTLMinutes=%d configuration", ttl))
@@
-                }
+                }
+
+                // Restore FeatureGate if we changed it
+                if featureGatePatched && len(originalFeatureGateSpecJSON) > 0 {
+                    g.By("  Restoring original FeatureGate spec")
+                    if _, err := configClient.ConfigV1().FeatureGates().Patch(ctx, "cluster", types.MergePatchType, originalFeatureGateSpecJSON, metav1.PatchOptions{}); err != nil {
+                        g.By(fmt.Sprintf("  Warning: failed to restore FeatureGate: %v", err))
+                    } else {
+                        _ = waitForAPIServerRollout(ctx, kubeClient, 20*time.Minute)
+                    }
+                }
             }()
@@
-            featureGate, err := configClient.ConfigV1().FeatureGates().Get(ctx, "cluster", metav1.GetOptions{})
+            featureGate, err := configClient.ConfigV1().FeatureGates().Get(ctx, "cluster", metav1.GetOptions{})
             o.Expect(err).NotTo(o.HaveOccurred(), "failed to get feature gate")
+            // capture original spec for restore
+            originalFeatureGateSpecJSON, _ = json.Marshal(map[string]interface{}{"spec": featureGate.Spec})
@@
-            if !isEnabled {
+            if !isEnabled {
                 g.By("Step 1b: Enabling EventTTL feature gate...")
@@
-                patchData := map[string]interface{}{
-                    "spec": map[string]interface{}{
-                        "featureSet": "CustomNoUpgrade",
-                        "customNoUpgrade": map[string]interface{}{
-                            "enabled": []string{"EventTTL"},
-                        },
-                    },
-                }
+                // merge-add EventTTL into existing lists
+                enabledSet := map[string]struct{}{}
+                for _, n := range featureGate.Spec.CustomNoUpgrade.Enabled { enabledSet[n] = struct{}{} }
+                enabledSet["EventTTL"] = struct{}{}
+                var enabledList []string
+                for k := range enabledSet { enabledList = append(enabledList, k) }
+                patchData := map[string]interface{}{
+                    "spec": map[string]interface{}{
+                        "featureSet": "CustomNoUpgrade",
+                        "customNoUpgrade": map[string]interface{}{
+                            "enabled":  enabledList,
+                            "disabled": featureGate.Spec.CustomNoUpgrade.Disabled,
+                        },
+                    },
+                }
                 patchBytes, err := json.Marshal(patchData)
                 o.Expect(err).NotTo(o.HaveOccurred())
@@
-                _, err = configClient.ConfigV1().FeatureGates().Patch(ctx, "cluster", types.MergePatchType, patchBytes, metav1.PatchOptions{})
+                _, err = configClient.ConfigV1().FeatureGates().Patch(ctx, "cluster", types.MergePatchType, patchBytes, metav1.PatchOptions{})
                 o.Expect(err).NotTo(o.HaveOccurred())
+                featureGatePatched = true

Also applies to: 57-76, 81-88, 120-145


3-21: Use apierrors.IsNotFound instead of string matching
String matching “not found” is brittle; use the typed helper.

@@
-import (
+import (
@@
     "k8s.io/apimachinery/pkg/types"
     "k8s.io/apimachinery/pkg/util/wait"
     "k8s.io/client-go/kubernetes"
+    apierrors "k8s.io/apimachinery/pkg/api/errors"
 )
@@
-    _, err = kubeClient.CoreV1().Events(testNamespace).Get(ctx, eventName, metav1.GetOptions{})
-    o.Expect(err).To(o.HaveOccurred(), "event should be deleted after TTL")
-    o.Expect(err.Error()).To(o.ContainSubstring("not found"), "event should return 'not found' error")
+    _, err = kubeClient.CoreV1().Events(testNamespace).Get(ctx, eventName, metav1.GetOptions{})
+    o.Expect(err).To(o.HaveOccurred(), "event should be deleted after TTL")
+    o.Expect(apierrors.IsNotFound(err)).To(o.BeTrue(), "expected NotFound error after TTL expiration")

Also applies to: 297-302


344-443: Rollout waiter: don’t assume 3 pods; gate on uniform new revision and Running
Hardcoding expectedPodCount=3 and “created after start” is topology‑ and timing‑sensitive. Derive count, ensure all pods Running, share one revision, and that revision differs from the initial one.

@@
-    // Count pods and check if we have new pods (created after rollout started)
-    notRunningCount := 0
-    newPodsCount := 0
-    runningNewPodsCount := 0
+    // Evaluate pod health and revisions
+    notRunningCount := 0
     var notRunningPods []string
-    var currentRevision string
+    sameRevision := true
+    currentRevision := ""
@@
-        // Check if this is a new pod (created after we started waiting for rollout)
-        isNewPod := pod.CreationTimestamp.Time.After(rolloutStartTime)
-
         if pod.Status.Phase != corev1.PodRunning {
             notRunningCount++
             notRunningPods = append(notRunningPods, fmt.Sprintf("%s (%s)", pod.Name, pod.Status.Phase))
         }
-
-        if isNewPod {
-            newPodsCount++
-            if pod.Status.Phase == corev1.PodRunning {
-                runningNewPodsCount++
-            }
-        }
-
-        // Track current revision
-        if rev, ok := pod.Labels["revision"]; ok && currentRevision == "" {
-            currentRevision = rev
-        }
+        if rev, ok := pod.Labels["revision"]; ok {
+            if currentRevision == "" { currentRevision = rev } else if currentRevision != rev { sameRevision = false }
+        } else {
+            sameRevision = false
+        }
@@
-    // Success: all expected pods are new and running
-    return allPodsNewAndRunning, nil
+    expectedPodCount := len(pods.Items)
+    allRunning := (notRunningCount == 0)
+    revChanged := (initialRevision == "") || (currentRevision != "" && currentRevision != initialRevision)
+    success := allRunning && sameRevision && revChanged && (expectedPodCount > 0)
+    return success, nil
🧹 Nitpick comments (4)
test/extended/tests-extension/README.md (1)

13-15: Unify invocation path and remove duplicate/contradictory examples

Two styles are shown for running tests; keep one to avoid confusion and ensure the path matches the new location.

Suggested edit:

  • Prefer the full path under test/extended/tests-extension for local runs, or explain both clearly (when/why).
  • Remove the second duplicate “How to Run the Tests Locally” header or rename it (“Running with KUBECONFIG”).

Example diff:

@@
-| `test/extended/tests-extension/cluster-kube-apiserver-operator-tests-ext run <test-name>` | Runs one specific test.                                                  |
+| `test/extended/tests-extension/cluster-kube-apiserver-operator-tests-ext run <test-name>` | Runs one specific test.                                                  |
@@
-export KUBECONFIG=path/to/kubeconfig
-test/extended/tests-extension/cluster-kube-apiserver-operator-tests-ext run <test-name>
-./cluster-kube-apiserver-operator-tests-ext run <test-name>
+export KUBECONFIG=path/to/kubeconfig
+test/extended/tests-extension/cluster-kube-apiserver-operator-tests-ext run <test-name>

Optional: Update the Cluster Bot example to a currently supported OCP version to avoid future staleness.

Also applies to: 25-27

Dockerfile.rhel7 (1)

7-7: Make artifact compression reproducible and idempotent

Use gzip flags to avoid non-deterministic timestamps and re-run failures.

-    && gzip test/extended/tests-extension/cluster-kube-apiserver-operator-tests-ext
+    && gzip -n -f test/extended/tests-extension/cluster-kube-apiserver-operator-tests-ext

Also confirm the Makefile’s tests-ext-build target emits the binary at the same path used here and in COPY.

Also applies to: 16-16

test/extended/tests-extension/event_ttl.go (2)

46-55: Shorten test runtime: poll for deletion instead of sleeping TTL+buffer; consider a single canonical TTL
Sleeping the full TTL+buffer per case makes the suite very long (especially 5/10/15m). Poll for NotFound with an overall timeout and run one canonical TTL by default; make others opt‑in (e.g., via DescribeTable or env gating).

Example change for polling:

-    time.Sleep(waitDuration)
-
-    // Verify event is deleted
-    actualExpirationTime := time.Now()
-    _, err = kubeClient.CoreV1().Events(testNamespace).Get(ctx, eventName, metav1.GetOptions{})
-    o.Expect(err).To(o.HaveOccurred(), "event should be deleted after TTL")
-    o.Expect(apierrors.IsNotFound(err)).To(o.BeTrue(), "expected NotFound error after TTL expiration")
+    pollStart := time.Now()
+    err = wait.PollUntilContextTimeout(ctx, 10*time.Second, waitDuration, false, func(_ context.Context) (bool, error) {
+        _, e := kubeClient.CoreV1().Events(testNamespace).Get(ctx, eventName, metav1.GetOptions{})
+        if apierrors.IsNotFound(e) { return true, nil }
+        if e != nil { g.By(fmt.Sprintf("  get event error: %v", e)) }
+        return false, nil
+    })
+    o.Expect(err).NotTo(o.HaveOccurred(), "timed out waiting for event deletion")
+    actualExpirationTime := time.Now()

And to cut matrix size:

- testValues := []int32{5, 10, 15}
+ // Keep a single canonical TTL by default to control runtime; gate others via env/label if needed.
+ testValues := []int32{5}

Also applies to: 262-296


469-516: Parse config.yaml instead of substring search for event-ttl
String matching can false‑positive. Parse YAML and assert apiServerArguments["event-ttl"][0] equals “m”.

If acceptable, I can follow up with a small helper that unmarshals the config and validates the exact field.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 0abb026 and 1a7a1f4.

⛔ Files ignored due to path filters (290)
  • go.sum is excluded by !**/*.sum
  • test/extended/tests-extension/go.sum is excluded by !**/*.sum
  • vendor/github.com/go-task/slim-sprig/v3/.editorconfig is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/.gitattributes is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/.gitignore is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/CHANGELOG.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/LICENSE.txt is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/README.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/Taskfile.yml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/crypto.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/date.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/defaults.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/dict.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/doc.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/functions.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/list.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/network.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/numeric.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/reflect.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/regex.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/strings.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/url.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/.gitignore is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/CONTRIBUTING.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/LICENSE is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/Makefile is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/OWNERS is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/README.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/RELEASING.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/config/deprecated.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/core_dsl.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/core_dsl_patch.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/decorator_dsl.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/deprecated_dsl.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/formatter/colorable_others.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/formatter/colorable_windows.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/formatter/formatter.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/build/build_command.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/command/abort.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/command/command.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/command/program.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/boostrap_templates.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/bootstrap_command.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/generate_command.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/generate_templates.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/generators_common.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/compile.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/gocovmerge.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/profiles_and_reports.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/run.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/test_suite.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/utils.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/verify_version.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/labels/labels_command.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/main.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/outline/ginkgo.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/outline/import.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/outline/outline.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/outline/outline_command.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/run/run_command.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/unfocus/unfocus_command.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/delta.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/delta_tracker.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/dependencies.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/package_hash.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/package_hashes.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/suite.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/watch_command.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo_cli_dependencies.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo_t_dsl.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/counter.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/failer.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/focus.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/global/init.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/group.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/interrupt_handler.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/sigquit_swallower_unix.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/sigquit_swallower_windows.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/node.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/ordering.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_unix.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_wasm.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_win.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/client_server.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_client.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_server.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/rpc_client.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/rpc_server.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/server_handler.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/progress_report.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/progress_report_bsd.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/progress_report_unix.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/progress_report_wasm.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/progress_report_win.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/progress_reporter_manager.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/report_entry.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/spec.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/spec_context.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/spec_patch.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/suite.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/suite_patch.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/testingtproxy/testing_t_proxy.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/tree.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/writer.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/reporters/default_reporter.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/reporters/deprecated_reporter.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/reporters/json_report.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/reporters/junit_report.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/reporters/reporter.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/reporters/teamcity_report.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/reporting_dsl.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/table_dsl.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/code_location.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/config.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/deprecated_types.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/deprecation_support.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/enum_support.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/errors.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/file_filter.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/flags.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/label_filter.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/report_entry.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/types.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/types_patch.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/version.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/.gitignore is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/CHANGELOG.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/CONTRIBUTING.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/LICENSE is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/README.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/RELEASING.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/format/format.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/gomega_dsl.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/internal/assertion.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/internal/async_assertion.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/internal/duration_bundle.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/internal/gomega.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/internal/gutil/post_ioutil.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/internal/gutil/using_ioutil.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/internal/polling_signal_error.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/internal/vetoptdesc.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/and.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/assignable_to_type_of_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/attributes_slice.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/be_a_directory.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/be_a_regular_file.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/be_an_existing_file.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/be_closed_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/be_comparable_to_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/be_element_of_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/be_empty_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/be_equivalent_to_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/be_false_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/be_identical_to.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/be_key_of_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/be_nil_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/be_numerically_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/be_sent_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/be_temporally_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/be_true_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/be_zero_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/consist_of.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/contain_element_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/contain_elements_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/contain_substring_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/equal_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/have_cap_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/have_each_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/have_exact_elements.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/have_existing_field_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/have_field.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/have_http_body_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/have_http_header_with_value_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/have_http_status_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/have_key_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/have_key_with_value_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/have_len_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/have_occurred_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/have_prefix_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/have_suffix_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/have_value.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/internal/miter/type_support_iter.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/internal/miter/type_support_noiter.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/match_error_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/match_json_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/match_regexp_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/match_xml_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/match_yaml_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/not.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/or.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/panic_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/receive_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/satisfy_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/semi_structured_data_support.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/succeed_matcher.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/support/goraph/bipartitegraph/bipartitegraph.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/support/goraph/bipartitegraph/bipartitegraphmatching.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/support/goraph/edge/edge.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/support/goraph/node/node.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/support/goraph/util/util.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/type_support.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/matchers/with_transform.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/types/types.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/LICENSE is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmd.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdimages/cmdimages.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdinfo/info.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdlist/list.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun/runsuite.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdrun/runtest.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/cmd/cmdupdate/update.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/dbtime/time.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extension.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/environment.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/result.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/result_writer.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/spec.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/task.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests/types.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/registry.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/extension/types.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/component.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/concurrency.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/environment.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/names.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/output.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/flags/suite.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/logging.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/parallel.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo/util.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/junit/types.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/LICENSE is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/README.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/byte.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/doc.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/empty.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/int.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/int32.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/int64.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/set.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/util/sets/string.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift-eng/openshift-tests-extension/pkg/version/version.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/net/html/atom/atom.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/net/html/atom/table.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/net/html/charset/charset.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/net/html/const.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/net/html/doc.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/net/html/doctype.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/net/html/entity.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/net/html/escape.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/net/html/foreign.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/net/html/iter.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/net/html/node.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/net/html/parse.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/net/html/render.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/net/html/token.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/charmap/charmap.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/charmap/tables.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/encoding.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/htmlindex/htmlindex.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/htmlindex/map.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/htmlindex/tables.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/internal/identifier/identifier.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/internal/identifier/mib.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/internal/internal.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/japanese/all.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/japanese/eucjp.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/japanese/iso2022jp.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/japanese/shiftjis.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/japanese/tables.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/korean/euckr.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/korean/tables.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/simplifiedchinese/all.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/simplifiedchinese/gbk.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/simplifiedchinese/hzgb2312.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/simplifiedchinese/tables.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/traditionalchinese/big5.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/traditionalchinese/tables.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/unicode/override.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/encoding/unicode/unicode.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/internal/utf8internal/utf8internal.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/runes/cond.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/text/runes/runes.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/tools/cover/profile.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/tools/go/ast/edge/edge.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/tools/go/ast/inspector/cursor.go is excluded by !vendor/**, !**/vendor/**
  • vendor/golang.org/x/tools/go/ast/inspector/inspector.go is excluded by !vendor/**, !**/vendor/**
📒 Files selected for processing (8)
  • .gitignore (1 hunks)
  • Dockerfile.rhel7 (2 hunks)
  • Makefile (3 hunks)
  • go.mod (0 hunks)
  • test/extended/tests-extension/README.md (2 hunks)
  • test/extended/tests-extension/cmd/main.go (1 hunks)
  • test/extended/tests-extension/event_ttl.go (1 hunks)
  • test/extended/tests-extension/go.mod (1 hunks)
💤 Files with no reviewable changes (1)
  • go.mod
✅ Files skipped from review due to trivial changes (2)
  • .gitignore
  • Makefile
🔇 Additional comments (1)
test/extended/tests-extension/go.mod (1)

1-15: No issues found—toolchain and dependency graph are aligned

Verification confirms Go version 1.24.0 is consistent across root module, tests-extension, and builder image (golang-1.24). The replace directive correctly references the local repo. Module resolution (tidy/vendor) succeeded without errors. No drift detected.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/extended/tests-extension/cmd/main.go (1)

28-28: Fix typo "apiserve" → "apiserver" in extension and suite names

Suite names must match for test discovery and labeling. The correct operator name is "cluster-kube-apiserver-operator" (confirmed in go.mod, imports, existing test references, and OpenShift documentation).

Apply to test/extended/tests-extension/cmd/main.go:

-	ext := e.NewExtension("openshift", "payload", "cluster-kube-apiserve-operator")
+	ext := e.NewExtension("openshift", "payload", "cluster-kube-apiserver-operator")
@@
-		Name:    "openshift/cluster-kube-apiserve-operator/conformance/parallel",
+		Name:    "openshift/cluster-kube-apiserver-operator/conformance/parallel",
@@
-		Name:    "openshift/cluster-kube-apiserve-operator/conformance/serial",
+		Name:    "openshift/cluster-kube-apiserver-operator/conformance/serial",
@@
-		Name:    "openshift/cluster-kube-apiserve-operator/optional/slow",
+		Name:    "openshift/cluster-kube-apiserver-operator/optional/slow",
@@
-		Name: "openshift/cluster-kube-apiserve-operator/all",
+		Name: "openshift/cluster-kube-apiserver-operator/all",

All five instances at lines 28, 32, 41, 50, 59 need correction.

♻️ Duplicate comments (3)
test/extended/tests-extension/event_ttl.go (3)

297-302: Use apierrors.IsNotFound instead of string matching

String checks are brittle; use typed error.

+    apierrors "k8s.io/apimachinery/pkg/api/errors"
@@
-    o.Expect(err).To(o.HaveOccurred(), "event should be deleted after TTL")
-    o.Expect(err.Error()).To(o.ContainSubstring("not found"), "event should return 'not found' error")
+    o.Expect(err).To(o.HaveOccurred(), "event should be deleted after TTL")
+    o.Expect(apierrors.IsNotFound(err)).To(o.BeTrue(), "expected NotFound error after TTL expiration")

344-443: Rollout waiter: detect revision change; don’t assume 3 pods or use creation-time heuristic

Handle SNO/HA topologies and reduce flakiness by verifying all pods Running, same revision, and revision != initial.

@@
-    // Count pods and check if we have new pods (created after rollout started)
-    notRunningCount := 0
-    newPodsCount := 0
-    runningNewPodsCount := 0
+    // Count pod readiness and revisions
+    notRunningCount := 0
     var notRunningPods []string
-    var currentRevision string
+    var currentRevision string
+    sameRevision := true
@@
-        // Check if this is a new pod (created after we started waiting for rollout)
-        isNewPod := pod.CreationTimestamp.Time.After(rolloutStartTime)
-
         if pod.Status.Phase != corev1.PodRunning {
             notRunningCount++
             notRunningPods = append(notRunningPods, fmt.Sprintf("%s (%s)", pod.Name, pod.Status.Phase))
         }
-
-        if isNewPod {
-            newPodsCount++
-            if pod.Status.Phase == corev1.PodRunning {
-                runningNewPodsCount++
-            }
-        }
-
-        // Track current revision
-        if rev, ok := pod.Labels["revision"]; ok && currentRevision == "" {
-            currentRevision = rev
-        }
+        // Track current revision and ensure all pods share it
+        if rev, ok := pod.Labels["revision"]; ok {
+            if currentRevision == "" { currentRevision = rev } else if currentRevision != rev { sameRevision = false }
+        } else {
+            sameRevision = false
+        }
@@
-    // We need ALL pods to be:
-    // 1. Running
-    // 2. Created after rollout started (new pods with new configuration)
-    // We expect 3 pods for a typical control plane
-    expectedPodCount := 3
-    allPodsNewAndRunning := (newPodsCount == expectedPodCount && runningNewPodsCount == expectedPodCount)
+    expectedPodCount := len(pods.Items)
+    allRunning := (notRunningCount == 0)
+    revChanged := (initialRevision == "") || (currentRevision != "" && currentRevision != initialRevision)
+    allPodsNewAndRunning := allRunning && sameRevision && revChanged && (expectedPodCount > 0)
@@
-            if notRunningCount > 0 {
-                g.By(fmt.Sprintf("  [Attempt %d] %d/%d pods running. Not running: %v. New pods: %d/%d running",
-                    attempt, len(pods.Items)-notRunningCount, len(pods.Items), notRunningPods, runningNewPodsCount, newPodsCount))
-            } else {
-                g.By(fmt.Sprintf("  [Attempt %d] All %d pods are running. New pods: %d/%d. Revision: %s",
-                    attempt, len(pods.Items), runningNewPodsCount, newPodsCount, currentRevision))
-            }
+            if notRunningCount > 0 {
+                g.By(fmt.Sprintf("  [Attempt %d] %d/%d pods running. Not running: %v. Current revision: %s",
+                    attempt, len(pods.Items)-notRunningCount, len(pods.Items), notRunningPods, currentRevision))
+            } else {
+                g.By(fmt.Sprintf("  [Attempt %d] All %d pods running. Revision: %s (initial: %s), sameRevision=%t",
+                    attempt, len(pods.Items), currentRevision, initialRevision, sameRevision))
+            }

57-76: Don’t overwrite FeatureGate; merge-add EventTTL and restore after test

Preserve existing FeatureGate entries and restore at the end. This was previously flagged.

Apply:

@@
 var (
   kubeClient     *kubernetes.Clientset
   configClient   *configclient.Clientset
   operatorClient *operatorclientset.Clientset
   ctx            context.Context
+  originalFeatureGateSpecJSON []byte
+  featureGatePatched bool
 )
@@
   featureGate, err := configClient.ConfigV1().FeatureGates().Get(ctx, "cluster", metav1.GetOptions{})
   o.Expect(err).NotTo(o.HaveOccurred(), "failed to get feature gate")
+  // capture original spec for restore
+  originalFeatureGateSpecJSON, _ = json.Marshal(map[string]interface{}{"spec": featureGate.Spec})
@@
- if !isEnabled {
+ if !isEnabled {
   g.By("Step 1b: Enabling EventTTL feature gate...")
@@
-  patchData := map[string]interface{}{
-    "spec": map[string]interface{}{
-      "featureSet": "CustomNoUpgrade",
-      "customNoUpgrade": map[string]interface{}{
-        "enabled": []string{"EventTTL"},
-      },
-    },
-  }
+  // merge-add EventTTL to existing enabled list; preserve disabled list
+  enabledSet := map[string]struct{}{}
+  for _, n := range featureGate.Spec.CustomNoUpgrade.Enabled { enabledSet[n] = struct{}{} }
+  if _, ok := enabledSet["EventTTL"]; !ok { enabledSet["EventTTL"] = struct{}{} }
+  var enabledList []string
+  for k := range enabledSet { enabledList = append(enabledList, k) }
+  patchData := map[string]interface{}{
+    "spec": map[string]interface{}{
+      "featureSet": "CustomNoUpgrade",
+      "customNoUpgrade": map[string]interface{}{
+        "enabled":  enabledList,
+        "disabled": featureGate.Spec.CustomNoUpgrade.Disabled,
+      },
+    },
+  }
@@
-  _, err = configClient.ConfigV1().FeatureGates().Patch(ctx, "cluster", types.MergePatchType, patchBytes, metav1.PatchOptions{})
+  _, err = configClient.ConfigV1().FeatureGates().Patch(ctx, "cluster", types.MergePatchType, patchBytes, metav1.PatchOptions{})
   o.Expect(err).NotTo(o.HaveOccurred())
+  featureGatePatched = true
@@
-  defer func() {
+  defer func() {
     g.By(fmt.Sprintf("\nCleaning up eventTTLMinutes=%d configuration", ttl))
@@
     }
+
+    // Restore FeatureGate if changed
+    if featureGatePatched && len(originalFeatureGateSpecJSON) > 0 {
+      g.By("  Restoring original FeatureGate spec")
+      if _, err := configClient.ConfigV1().FeatureGates().Patch(ctx, "cluster", types.MergePatchType, originalFeatureGateSpecJSON, metav1.PatchOptions{}); err != nil {
+        g.By(fmt.Sprintf("  Warning: failed to restore FeatureGate: %v", err))
+      } else {
+        _ = waitForAPIServerRollout(ctx, kubeClient, 20*time.Minute)
+      }
+    }
   }()

Also applies to: 81-114, 120-152

🧹 Nitpick comments (3)
Dockerfile.rhel7 (1)

7-7: Harden gzip for reproducibility and idempotency

Use -n (omit timestamp), -9 (max compression), -f (overwrite).

-    && gzip test/extended/tests-extension/cluster-kube-apiserver-operator-tests-ext
+    && gzip -n -9 -f test/extended/tests-extension/cluster-kube-apiserver-operator-tests-ext
test/extended/tests-extension/event_ttl.go (2)

465-517: Parse YAML instead of substring search for config.yaml

Avoid false positives; inspect apiServerArguments.event-ttl value.

-    // Check if event-ttl is in the config
-    hasEventTTL := strings.Contains(configData, "event-ttl")
-    ...
-    hasCorrectValue := strings.Contains(configData, fmt.Sprintf("- %s", expectedTTL)) ||
-        strings.Contains(configData, fmt.Sprintf("\"%s\"", expectedTTL)) ||
-        strings.Contains(configData, fmt.Sprintf("'%s'", expectedTTL))
+    type apiserverConfig struct {
+        APIServerArguments map[string][]string `json:"apiServerArguments" yaml:"apiServerArguments"`
+    }
+    var cfg apiserverConfig
+    o.Expect(yaml.Unmarshal([]byte(configData), &cfg)).NotTo(o.HaveOccurred(), "failed to parse config.yaml")
+    vals := cfg.APIServerArguments["event-ttl"]
+    o.Expect(vals).NotTo(o.BeEmpty(), "event-ttl not found in apiServerArguments")
+    hasCorrectValue := false
+    for _, v := range vals { if v == expectedTTL { hasCorrectValue = true; break } }

Note: add import "sigs.k8s.io/yaml".


262-296: Optional: poll for deletion instead of fixed sleep to reduce test time and flakes

Poll until NotFound with a max timeout (TTL + buffer), rather than sleeping the full duration.

-    time.Sleep(waitDuration)
-
-    // Verify event is deleted
-    actualExpirationTime := time.Now()
-    _, err = kubeClient.CoreV1().Events(testNamespace).Get(ctx, eventName, metav1.GetOptions{})
+    var actualExpirationTime time.Time
+    _ = wait.PollUntilContextTimeout(ctx, 10*time.Second, waitDuration, false, func(_ context.Context) (bool, error) {
+        _, err := kubeClient.CoreV1().Events(testNamespace).Get(ctx, eventName, metav1.GetOptions{})
+        if apierrors.IsNotFound(err) { actualExpirationTime = time.Now(); return true, nil }
+        return false, nil
+    })
+    _, err = kubeClient.CoreV1().Events(testNamespace).Get(ctx, eventName, metav1.GetOptions{})

Requires apierrors import (already suggested).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 1a7a1f4 and 7f653c9.

⛔ Files ignored due to path filters (132)
  • go.sum is excluded by !**/*.sum
  • test/extended/tests-extension/go.sum is excluded by !**/*.sum
  • vendor/github.com/go-task/slim-sprig/v3/.editorconfig is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/.gitattributes is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/.gitignore is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/CHANGELOG.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/LICENSE.txt is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/README.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/Taskfile.yml is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/crypto.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/date.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/defaults.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/dict.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/doc.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/functions.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/list.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/network.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/numeric.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/reflect.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/regex.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/strings.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/go-task/slim-sprig/v3/url.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/.gitignore is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/CONTRIBUTING.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/LICENSE is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/Makefile is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/OWNERS is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/README.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/RELEASING.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/config/deprecated.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/core_dsl.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/core_dsl_patch.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/decorator_dsl.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/deprecated_dsl.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/formatter/colorable_others.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/formatter/colorable_windows.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/formatter/formatter.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/build/build_command.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/command/abort.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/command/command.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/command/program.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/boostrap_templates.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/bootstrap_command.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/generate_command.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/generate_templates.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/generators/generators_common.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/compile.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/gocovmerge.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/profiles_and_reports.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/run.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/test_suite.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/utils.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/internal/verify_version.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/labels/labels_command.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/main.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/outline/ginkgo.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/outline/import.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/outline/outline.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/outline/outline_command.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/run/run_command.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/unfocus/unfocus_command.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/delta.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/delta_tracker.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/dependencies.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/package_hash.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/package_hashes.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/suite.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo/watch/watch_command.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo_cli_dependencies.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/ginkgo_t_dsl.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/counter.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/failer.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/focus.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/global/init.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/group.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/interrupt_handler.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/sigquit_swallower_unix.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/sigquit_swallower_windows.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/node.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/ordering.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_unix.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_wasm.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_win.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/client_server.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_client.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_server.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/rpc_client.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/rpc_server.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/server_handler.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/progress_report.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/progress_report_bsd.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/progress_report_unix.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/progress_report_wasm.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/progress_report_win.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/progress_reporter_manager.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/report_entry.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/spec.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/spec_context.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/spec_patch.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/suite.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/suite_patch.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/testingtproxy/testing_t_proxy.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/tree.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/internal/writer.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/reporters/default_reporter.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/reporters/deprecated_reporter.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/reporters/json_report.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/reporters/junit_report.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/reporters/reporter.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/reporters/teamcity_report.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/reporting_dsl.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/table_dsl.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/code_location.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/config.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/deprecated_types.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/deprecation_support.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/enum_support.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/errors.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/file_filter.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/flags.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/label_filter.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/report_entry.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/types.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/types_patch.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/ginkgo/v2/types/version.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/.gitignore is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/CHANGELOG.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/CONTRIBUTING.md is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/LICENSE is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/onsi/gomega/README.md is excluded by !vendor/**, !**/vendor/**
📒 Files selected for processing (8)
  • .gitignore (1 hunks)
  • Dockerfile.rhel7 (2 hunks)
  • Makefile (3 hunks)
  • go.mod (0 hunks)
  • test/extended/tests-extension/README.md (2 hunks)
  • test/extended/tests-extension/cmd/main.go (1 hunks)
  • test/extended/tests-extension/event_ttl.go (1 hunks)
  • test/extended/tests-extension/go.mod (1 hunks)
💤 Files with no reviewable changes (1)
  • go.mod
🚧 Files skipped from review as they are similar to previous changes (3)
  • Makefile
  • test/extended/tests-extension/README.md
  • .gitignore
🔇 Additional comments (3)
test/extended/tests-extension/cmd/main.go (1)

23-23: LGTM: side-effect import path update is correct

The new registration path matches the relocated tests-extension module.

Dockerfile.rhel7 (1)

16-16: LGTM: updated COPY matches new artifact location

Path aligns with the build-stage gzip output.

test/extended/tests-extension/go.mod (1)

72-74: Ginkgo OpenShift fork replace is intentional and compatible

The replace directive is explicit in go.mod and Ginkgo v2.6.1 is confirmed compatible with Gomega v1.36.1. This follows the standard OpenShift fork pattern used in test-extension modules.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 7f653c9 and 649261f.

⛔ Files ignored due to path filters (1)
  • test/extended/tests-extension/go.sum is excluded by !**/*.sum
📒 Files selected for processing (1)
  • test/extended/tests-extension/go.mod (1 hunks)
🔇 Additional comments (3)
test/extended/tests-extension/go.mod (3)

8-8: No issues found—the code is correctly configured.

Verification confirms:

  • The openshift/client-go pseudo-version (v0.0.0-20251015124057-db0dee36e235) is valid and recent (Oct 15, 2025)
  • The fake version (v0.0.0-00010101000000-000000000000) follows Go module conventions for local replacements
  • The replace directive path (../../..) correctly resolves from test/extended/tests-extension/ to the root module

3-3: Consider upgrading from Go 1.24.0 to a current patch version for security improvements.

Kubernetes v1.34 was built with Go 1.24.5, confirming ecosystem compatibility with Go 1.24. However, Go 1.24.8 and 1.24.9 include important security fixes released since the initial 1.24.0 in February 2025. Maintaining Go 1.24.0 forgoes months of accumulated security patches. Consider updating the go.mod to use the latest stable patch version (currently 1.24.9 as of October 2025) to ensure the test extension benefits from security improvements while maintaining compatibility with the broader Kubernetes ecosystem.


12-14: No changes required. The Kubernetes and OpenShift dependencies are correctly pinned and compatible.

The k8s.io/client-go v0.34.1 corresponds to Kubernetes v1.34.1, released September 9, 2025—it is not a "future version" as claimed, but rather a current stable release. Additionally, the root module (go.mod) already pins identical k8s.io packages to v0.34.1:

  • k8s.io/api v0.34.1
  • k8s.io/apimachinery v0.34.1
  • k8s.io/client-go v0.34.1
  • (and others at v0.34.1)

The test module's dependencies are synchronized with the root module, and both use the same OpenShift client-go pseudo-version (v0.0.0-20251015124057-db0dee36e235). The test extension is self-contained and explicitly configured to run against OpenShift clusters via its isolated test runner; no version conflict exists with the root module.

Likely an incorrect or invalid review comment.

Comment on lines +74 to +77
replace (
github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20250416174521-4eb003743b54
github.com/openshift/cluster-kube-apiserver-operator => ../../..
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify OpenShift ginkgo fork is actively maintained.

The replace directive uses an OpenShift fork of ginkgo/v2 (v2.6.1-0.20250416174521-4eb003743b54) instead of the upstream version (v2.22.0). Verify that:

  • This fork is actively maintained by OpenShift
  • The version is recent and includes security patches
  • The fork is compatible with the upstream version v2.22.0 in terms of API surface

🌐 Web query:

What is the status and maintenance of the OpenShift onsi-ginkgo fork?

💡 Result:

Short answer: OpenShift does not maintain a separate, actively maintained "onsi-ginkgo" fork — OpenShift projects use the upstream onsi/ginkgo (v2) releases and keep their Go module dependencies updated in their repos. See the upstream onsi/ginkgo repository/releases and OpenShift docs showing they depend on github.com/onsi/ginkgo/v2. [1][2]

Sources:

  • onsi/ginkgo (upstream) — repo & releases. [1]
  • OpenShift documentation (operators/dev guide) showing dependency on github.com/onsi/ginkgo/v2. [2]

The web search result suggests OpenShift uses upstream ginkgo, but this conflicts with the code showing an explicit OpenShift fork URL. Let me search more specifically for this fork's existence and purpose.

[/web_search]


🌐 Web query:

github.com/openshift/onsi-ginkgo repository fork maintenance

💡 Result:

Short answer: There is no public GitHub repository named openshift/onsi-ginkgo that is kept up-to-date. OpenShift projects generally consume and sometimes vendor or patch onsi/ginkgo inside their own repos (e.g. a forked copy appears in OpenShift Origin/Godeps or as a versioned module like github.com/onsi/ginkgo@...-origin), but the canonical upstream maintained project is onsi/ginkgo. Use the upstream onsi/ginkgo for the official, actively maintained source and treat any OpenShift-internal copies as vendored/patched for OpenShift's own needs. [1][2]

Sources

  • onsi/ginkgo (official upstream repo). [1]
  • Examples showing OpenShift/origin using a vendored/modified ginkgo (versioned with “-origin” / present in Godeps). [2]

The web search results indicate that the openshift/onsi-ginkgo fork referenced in the go.mod file does not appear to exist as a public, actively maintained repository. Let me search for context about why this specific fork reference was chosen and whether there are known issues with upstream ginkgo that necessitate it.

[/web_search]


🌐 Web query:

OpenShift ginkgo fork why use instead upstream v2

💡 Result:

Short answer — because switching to upstream Ginkgo v2 changes test behaviour and tooling in ways that would break or require large work to migrate OpenShift’s long-standing test suites, CI/reporting, and In‑tree test tooling. Typical reasons OpenShift (and related projects) keep a fork or pinned version instead of moving to upstream v2 are:

  • Compatibility with existing tests and helpers: OpenShift has many extended/e2e/extended-style tests and custom test helpers that rely on Ginkgo v1 semantics and reporters; moving to v2 can change lifecycle hooks, reporting and output behaviour. [1][2]
  • Test-reporting and JUnit/AfterSuite/parallel semantics: Ginkgo v2 changed how reports/AfterSuite and parallel test output are handled (and introduced new decorators like Serial). That can break CI pipelines that merge JUnit files or rely on previous ordering/aggregation. Projects often delay migration until tooling and CI are adapted. [1]
  • Custom patches / vendoring and stability: Large downstream projects often vendor a fork to apply fixes, add integration with their test infrastructure, or pin behavior for stable releases rather than constantly chasing upstream API/behavior changes. [2]
  • Migration cost/risk: The effort to update thousands of tests, update CI, and revalidate flakes/regressions is non‑trivial; teams prefer a staged migration or keep a maintained fork until upstream fits their needs.

If you want, I can:

  • look up specific OpenShift issues/PRs that discuss a Ginkgo fork or migration plan, or
  • find concrete examples of behavior changes between Ginkgo v1 and v2 (Serial, reporting, AfterSuite) and how they affect e2e pipelines.

Sources:

  • discussion about Ginkgo V2 Serial / reporting changes (kubevirt dev thread). [1]
  • OpenShift repositories vendoring/depending on specific Ginkgo versions (Godeps / origin references). [2]

The OpenShift onsi-ginkgo fork referenced in the go.mod cannot be verified—the repository does not appear to exist publicly.

Web searches confirm there is no public GitHub repository named openshift/onsi-ginkgo that is kept up-to-date or actively maintained. The go.mod file references github.com/openshift/onsi-ginkgo/v2 with a commit hash, but this URL is unverifiable.

While OpenShift projects typically vendor or patch ginkgo internally and may keep a fork to maintain compatibility with existing tests, custom patches, and CI pipelines rather than upgrading to upstream v2, the specific fork URL in this go.mod file should either:

  • Be verified to exist (including whether it's private and accessible)
  • Be updated to reference the actual upstream github.com/onsi/ginkgo/v2 or the correct OpenShift-maintained version

This is a dependency resolution risk and requires clarification before merging.

🤖 Prompt for AI Agents
In test/extended/tests-extension/go.mod around lines 74-77 the replace directive
points to github.com/openshift/onsi-ginkgo/v2 which is not publicly verifiable;
update this dependency to a verifiable source by either changing the replace to
the upstream module (github.com/onsi/ginkgo/v2) at the intended tag/commit
(e.g., v2.6.1) or, if an OpenShift-private fork is intended, replace it with a
correct accessible URL or a local path vendored copy and document access, then
run go mod tidy to refresh go.sum and ensure builds resolve.

Remove k8s.io replace directives from test module to resolve version conflicts.

The test module should not override k8s.io versions with replace directives
as this creates conflicts between:
- k8s.io/apimachinery v0.34.1 (required by dependencies)
- Forced downgrade to v0.33.2 (via replace)
- sigs.k8s.io/structured-merge-diff v4 vs v6 incompatibility

By removing k8s.io replaces and keeping only the necessary ones
(ginkgo and cluster-kube-apiserver-operator local replace), the test
module can naturally resolve to compatible versions.

This allows the CI build to succeed without vendoring in the test module.
@openshift-ci openshift-ci bot added the do-not-merge/invalid-owners-file Indicates that a PR should not merge because it has an invalid OWNERS file in it. label Oct 24, 2025
@@ -0,0 +1,29 @@
reviewers:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you may want to check this list again, many of those folks are not even at the company anymore. Some of them are now working for different components or other orgs within redhat.

@openshift-ci openshift-ci bot removed the do-not-merge/invalid-owners-file Indicates that a PR should not merge because it has an invalid OWNERS file in it. label Oct 24, 2025
- Updated all binary paths to use test/extended/tests-extension/bin/
- Added Test Module Structure section explaining dependency isolation
- Added Makefile and bin/ directory to structure diagram
- Clarified build output location in Makefile commands table
- Updated all command examples to reference bin/ subdirectory
- Fixed metadata file path in note
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Oct 24, 2025

@gangwgr: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/e2e-aws-ovn-serial-1of2 9752fbd link true /test e2e-aws-ovn-serial-1of2
ci/prow/e2e-aws-ovn 9752fbd link true /test e2e-aws-ovn
ci/prow/okd-scos-e2e-aws-ovn 9752fbd link false /test okd-scos-e2e-aws-ovn
ci/prow/e2e-tests-extension 9752fbd link true /test e2e-tests-extension

Full PR test history. Your PR dashboard.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@benluddy
Copy link
Contributor

Could you open another PR focused on splitting out the extension test module, please? If any issues come up in the CI signal later, it would be easier to distinguish the effects of the boilerplate changes from the reliability of the event-ttl test itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants