Repository: kubernetes-sigs/kwok
Pull request #426 (2023-03-28 05:50:35)

Fix flake test
<!--  Thanks for sending a pull request!  Here are some tips for you:

1. If this is your first time, please read our contributor guidelines: https://git.k8s.io/community/contributors/guide/first-contribution.md#your-first-contribution and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide
2. Please label this pull request according to what type of issue you are addressing, especially if this is a release targeted pull request. For reference on required PR/issue labels, read here:
https://git.k8s.io/community/contributors/devel/sig-release/release.md#issuepr-kind-label
3. Ensure you have added or ran the appropriate tests for your PR: https://git.k8s.io/community/contributors/devel/sig-testing/testing.md
4. If you want *faster* PR reviews, read how: https://git.k8s.io/community/contributors/guide/pull-requests.md#best-practices-for-faster-reviews
5. If the PR is unfinished, see how to mark it: https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests
-->

#### What type of PR is this?

<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->

/kind flake

#### What this PR does / why we need it:

#### Which issue(s) this PR fixes:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #

#### Special notes for your reviewer:

#### Does this PR introduce a user-facing change?
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

For more information on release notes see: https://git.k8s.io/community/contributors/guide/release-notes.md
-->
```release-note

```

#### Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:

<!--
This section can be blank if this pull request does not require a release note.

When adding links which point to resources within git repositories, like
KEPs or supporting documentation, please reference a specific commit and avoid
linking directly to the master branch. This ensures that links reference a
specific point in time, rather than a document that may change over time.

See here for guidance on getting permanent links to files: https://help.github.com/en/articles/getting-permanent-links-to-files

Please use the following format for linking documentation:
- [KEP]: <link>
- [Usage]: <link>
- [Other doc]: <link>
-->
```docs

```


k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/kwok/pull/426#" title="Author self-approved">wzshiming</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fkwok).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/kwok/blob/main/OWNERS)~~ [wzshiming]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *k8s-kwok* processing.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | ac34932a679fc92ed0fca13fe56f4e097cf84fa2 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/k8s-kwok/deploys/6422802d65907c0008f8c68e |
k8s-ci-robot: @wzshiming: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-kwok-unit-test-main | 9b5786eccc84d2e4c0756e37b7a9ade739753ce2 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/426/pull-kwok-unit-test-main/1640601556002803712) | true | `/test pull-kwok-unit-test-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=kwok&pr=426). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Awzshiming). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/kwok/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @wzshiming: 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
--- | --- | --- | --- | ---
pull-kwok-unit-test-main | 9b5786eccc84d2e4c0756e37b7a9ade739753ce2 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/426/pull-kwok-unit-test-main/1640601556002803712) | true | `/test pull-kwok-unit-test-main`
pull-kwok-verify-main | 148274c8cb36900a8a8221bfb326848d451c6d49 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/426/pull-kwok-verify-main/1640603213986009088) | true | `/test pull-kwok-verify-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=kwok&pr=426). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Awzshiming). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/kwok/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @wzshiming: 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
--- | --- | --- | --- | ---
pull-kwok-verify-main | 148274c8cb36900a8a8221bfb326848d451c6d49 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/426/pull-kwok-verify-main/1640603213986009088) | true | `/test pull-kwok-verify-main`
pull-kwok-unit-test-main | 148274c8cb36900a8a8221bfb326848d451c6d49 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/426/pull-kwok-unit-test-main/1640603214090866688) | true | `/test pull-kwok-unit-test-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=kwok&pr=426). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Awzshiming). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/kwok/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @wzshiming: 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
--- | --- | --- | --- | ---
pull-kwok-unit-test-main | 148274c8cb36900a8a8221bfb326848d451c6d49 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/426/pull-kwok-unit-test-main/1640603214090866688) | true | `/test pull-kwok-unit-test-main`
pull-kwok-verify-main | 85da9a20480f54b525d96f72b3ea55208fc4c709 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/426/pull-kwok-verify-main/1640603960983162880) | true | `/test pull-kwok-verify-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=kwok&pr=426). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Awzshiming). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/kwok/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @wzshiming: 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
--- | --- | --- | --- | ---
pull-kwok-verify-main | 85da9a20480f54b525d96f72b3ea55208fc4c709 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/426/pull-kwok-verify-main/1640603960983162880) | true | `/test pull-kwok-verify-main`
pull-kwok-unit-test-main | 85da9a20480f54b525d96f72b3ea55208fc4c709 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/426/pull-kwok-unit-test-main/1640603961071243264) | true | `/test pull-kwok-unit-test-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=kwok&pr=426). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Awzshiming). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/kwok/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->

Patch:
diff --git a/pkg/kwok/controllers/controller.go b/pkg/kwok/controllers/controller.go
index 949a84cc..d300e7fb 100644
--- a/pkg/kwok/controllers/controller.go
+++ b/pkg/kwok/controllers/controller.go
@@ -32,7 +32,6 @@ import (
 	"sigs.k8s.io/yaml"
 
 	"sigs.k8s.io/kwok/pkg/apis/internalversion"
-	"sigs.k8s.io/kwok/pkg/consts"
 )
 
 var (
@@ -58,9 +57,6 @@ var (
 			}
 			return data, nil
 		},
-		"Version": func() string {
-			return consts.Version
-		},
 	}
 )
 
@@ -128,7 +124,6 @@ func NewController(conf Config) (*Controller, error) {
 		NodePort:                              conf.NodePort,
 		DisregardStatusWithAnnotationSelector: conf.DisregardStatusWithAnnotationSelector,
 		DisregardStatusWithLabelSelector:      conf.DisregardStatusWithLabelSelector,
-		ManageNodesWithAnnotationSelector:     conf.ManageNodesWithAnnotationSelector,
 		ManageNodesWithLabelSelector:          conf.ManageNodesWithLabelSelector,
 		NodeSelectorFunc:                      nodeSelectorFunc,
 		LockPodsOnNodeFunc: func(ctx context.Context, nodeName string) error {
diff --git a/pkg/kwok/controllers/node_controller.go b/pkg/kwok/controllers/node_controller.go
index c89e1f9f..92bfb285 100644
--- a/pkg/kwok/controllers/node_controller.go
+++ b/pkg/kwok/controllers/node_controller.go
@@ -92,7 +92,6 @@ type NodeController struct {
 	nodePort                              int
 	disregardStatusWithAnnotationSelector labels.Selector
 	disregardStatusWithLabelSelector      labels.Selector
-	manageNodesWithAnnotationSelector     string
 	manageNodesWithLabelSelector          string
 	nodeSelectorFunc                      func(node *corev1.Node) bool
 	lockPodsOnNodeFunc                    func(ctx context.Context, nodeName string) error
@@ -113,7 +112,6 @@ type NodeControllerConfig struct {
 	LockPodsOnNodeFunc                    func(ctx context.Context, nodeName string) error
 	DisregardStatusWithAnnotationSelector string
 	DisregardStatusWithLabelSelector      string
-	ManageNodesWithAnnotationSelector     string
 	ManageNodesWithLabelSelector          string
 	NodeIP                                string
 	NodeName                              string
@@ -152,7 +150,6 @@ func NewNodeController(conf NodeControllerConfig) (*NodeController, error) {
 		nodeSelectorFunc:                      conf.NodeSelectorFunc,
 		disregardStatusWithAnnotationSelector: disregardStatusWithAnnotationSelector,
 		disregardStatusWithLabelSelector:      disregardStatusWithLabelSelector,
-		manageNodesWithAnnotationSelector:     conf.ManageNodesWithAnnotationSelector,
 		manageNodesWithLabelSelector:          conf.ManageNodesWithLabelSelector,
 		lockPodsOnNodeFunc:                    conf.LockPodsOnNodeFunc,
 		nodeIP:                                conf.NodeIP,
diff --git a/pkg/utils/wait/doc.go b/pkg/utils/wait/doc.go
new file mode 100644
index 00000000..420289d2
--- /dev/null
+++ b/pkg/utils/wait/doc.go
@@ -0,0 +1,18 @@
+/*
+Copyright 2023 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package wait provides tools for polling or listening for changes
+package wait
diff --git a/pkg/utils/wait/wait.go b/pkg/utils/wait/wait.go
new file mode 100644
index 00000000..50a2668e
--- /dev/null
+++ b/pkg/utils/wait/wait.go
@@ -0,0 +1,108 @@
+/*
+Copyright 2023 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package wait
+
+import (
+	"context"
+	"time"
+
+	apimachinerywait "k8s.io/apimachinery/pkg/util/wait"
+)
+
+const (
+	defaultPollTimeout         = 1 * time.Minute
+	defaultPollInterval        = 1 * time.Second
+	defaultPollContinueOnError = 5
+)
+
+// Options is used to configure the wait
+type Options struct {
+	// Interval is used to specify the interval for the wait
+	Interval time.Duration
+	// Timeout is used to specify the timeout for the wait
+	Timeout time.Duration
+	// Immediate is used to indicate if the wait should be immediate
+	Immediate bool
+	// ContinueOnError is used to specify the number of times the wait should continue on error
+	ContinueOnError int
+}
+
+// OptionFunc is a function that can be used to configure the wait
+type OptionFunc func(*Options)
+
+// WithContinueOnError configures the number of times the wait should continue on error
+func WithContinueOnError(continueOnError int) OptionFunc {
+	return func(options *Options) {
+		options.ContinueOnError = continueOnError
+	}
+}
+
+// WithTimeout configures the timeout for the wait
+func WithTimeout(timeout time.Duration) OptionFunc {
+	return func(options *Options) {
+		options.Timeout = timeout
+	}
+}
+
+// WithInterval configures the interval for the wait
+func WithInterval(interval time.Duration) OptionFunc {
+	return func(options *Options) {
+		options.Interval = interval
+	}
+}
+
+// WithImmediate configures the wait to be immediate
+func WithImmediate() OptionFunc {
+	return func(options *Options) {
+		options.Immediate = true
+	}
+}
+
+// Poll polls a condition until it returns true, an error, or the timeout is reached.
+func Poll(ctx context.Context, conditionFunc apimachinerywait.ConditionWithContextFunc, opts ...OptionFunc) error {
+	options := &Options{
+		Interval:        defaultPollInterval,
+		Timeout:         defaultPollTimeout,
+		ContinueOnError: defaultPollContinueOnError,
+		Immediate:       false,
+	}
+
+	for _, fn := range opts {
+		fn(options)
+	}
+
+	cf := conditionFunc
+
+	if options.ContinueOnError != 0 {
+		count := 0
+		cf = func(ctx context.Context) (bool, error) {
+			done, err := conditionFunc(ctx)
+			if err != nil {
+				count++
+				if count == options.ContinueOnError {
+					return false, err
+				}
+			}
+			return done, nil
+		}
+	}
+
+	if options.Immediate {
+		return apimachinerywait.PollImmediateWithContext(ctx, options.Interval, options.Timeout, cf)
+	}
+	return apimachinerywait.PollWithContext(ctx, options.Interval, options.Timeout, cf)
+}
diff --git a/stages/node-fast.yaml b/stages/node-fast.yaml
index ed3ca8e8..f061c1fe 100644
--- a/stages/node-fast.yaml
+++ b/stages/node-fast.yaml
@@ -66,10 +66,10 @@ spec:
       nodeInfo:
         architecture: {{ with .architecture }} {{ . }} {{ else }} "amd64" {{ end }}
         bootID: {{ with .bootID }} {{ . }} {{ else }} "" {{ end }}
-        containerRuntimeVersion: {{ with .containerRuntimeVersion }} {{ . }} {{ else }} "kwok-{{ Version }}" {{ end }}
-        kernelVersion: {{ with .kernelVersion }} {{ . }} {{ else }} "kwok-{{ Version }}" {{ end }}
-        kubeProxyVersion: {{ with .kubeProxyVersion }} {{ . }} {{ else }} "kwok-{{ Version }}" {{ end }}
-        kubeletVersion: {{ with .kubeletVersion }} {{ . }} {{ else }} "kwok-{{ Version }}" {{ end }}
+        containerRuntimeVersion: {{ with .containerRuntimeVersion }} {{ . }} {{ else }} "" {{ end }}
+        kernelVersion: {{ with .kernelVersion }} {{ . }} {{ else }} "" {{ end }}
+        kubeProxyVersion: {{ with .kubeProxyVersion }} {{ . }} {{ else }} "fake" {{ end }}
+        kubeletVersion: {{ with .kubeletVersion }} {{ . }} {{ else }} "fake" {{ end }}
         machineID: {{ with .machineID }} {{ . }} {{ else }} "" {{ end }}
         operatingSystem: {{ with .operatingSystem }} {{ . }} {{ else }} "linux" {{ end }}
         osImage: {{ with .osImage }} {{ . }} {{ else }} "" {{ end }}


Test patch:
diff --git a/pkg/kwok/controllers/node_controller_test.go b/pkg/kwok/controllers/node_controller_test.go
index 11a0f63c..764dec1a 100644
--- a/pkg/kwok/controllers/node_controller_test.go
+++ b/pkg/kwok/controllers/node_controller_test.go
@@ -27,10 +27,10 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/client-go/kubernetes/fake"
 
 	"sigs.k8s.io/kwok/pkg/log"
+	"sigs.k8s.io/kwok/pkg/utils/wait"
 	"sigs.k8s.io/kwok/stages"
 )
 
@@ -94,7 +94,7 @@ func TestNodeController(t *testing.T) {
 	}
 
 	var node0 *corev1.Node
-	err = wait.PollUntilWithContext(ctx, time.Second, func(ctx context.Context) (done bool, err error) {
+	err = wait.Poll(ctx, func(ctx context.Context) (done bool, err error) {
 		node0, err = clientset.CoreV1().Nodes().Get(ctx, "node0", metav1.GetOptions{})
 		if err != nil {
 			return false, fmt.Errorf("failed to get node0: %w", err)
@@ -116,7 +116,7 @@ func TestNodeController(t *testing.T) {
 		t.Fatal(fmt.Errorf("failed to create node1: %w", err))
 	}
 
-	err = wait.PollUntilWithContext(ctx, time.Second, func(ctx context.Context) (done bool, err error) {
+	err = wait.Poll(ctx, func(ctx context.Context) (done bool, err error) {
 		nodeSize := nodes.Size()
 		if nodeSize != 2 {
 			return false, fmt.Errorf("want 2 nodes, got %d", nodeSize)
@@ -135,19 +135,25 @@ func TestNodeController(t *testing.T) {
 		t.Fatal(fmt.Errorf("node1 want 8 cpu, got %v", node1.Status.Allocatable[corev1.ResourceCPU]))
 	}
 
-	list, err := clientset.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
-	if err != nil {
-		t.Fatal(fmt.Errorf("failed to list nodes: %w", err))
-	}
-	for i, node := range list.Items {
-		if nodeSelectorFunc(&list.Items[i]) {
-			if node.Status.Phase != corev1.NodeRunning {
-				t.Fatal(fmt.Errorf("want node %s to be running, got %s", node.Name, node.Status.Phase))
-			}
-		} else {
-			if node.Status.Phase == corev1.NodeRunning {
-				t.Fatal(fmt.Errorf("want node %s to be not running, got %s", node.Name, node.Status.Phase))
+	err = wait.Poll(ctx, func(ctx context.Context) (done bool, err error) {
+		list, err := clientset.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
+		if err != nil {
+			return false, fmt.Errorf("failed to list nodes: %w", err)
+		}
+		for i, node := range list.Items {
+			if nodeSelectorFunc(&list.Items[i]) {
+				if node.Status.Phase != corev1.NodeRunning {
+					return false, fmt.Errorf("want node %s to be running, got %s", node.Name, node.Status.Phase)
+				}
+			} else {
+				if node.Status.Phase == corev1.NodeRunning {
+					return false, fmt.Errorf("want node %s to be not running, got %s", node.Name, node.Status.Phase)
+				}
 			}
 		}
+		return true, nil
+	})
+	if err != nil {
+		t.Fatal(err)
 	}
 }
diff --git a/pkg/kwok/controllers/pod_controller_test.go b/pkg/kwok/controllers/pod_controller_test.go
index 5a9b8cd4..eba828cc 100644
--- a/pkg/kwok/controllers/pod_controller_test.go
+++ b/pkg/kwok/controllers/pod_controller_test.go
@@ -27,10 +27,10 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
-	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/client-go/kubernetes/fake"
 
 	"sigs.k8s.io/kwok/pkg/log"
+	"sigs.k8s.io/kwok/pkg/utils/wait"
 	"sigs.k8s.io/kwok/stages"
 )
 
@@ -259,44 +259,37 @@ func TestPodController(t *testing.T) {
 	}
 
 	var list *corev1.PodList
-	err = wait.PollUntilWithContext(ctx, time.Second, func(ctx context.Context) (done bool, err error) {
+	err = wait.Poll(ctx, func(ctx context.Context) (done bool, err error) {
 		list, err = clientset.CoreV1().Pods("default").List(ctx, metav1.ListOptions{})
 		if err != nil {
-			t.Log(fmt.Errorf("list pods error: %w", err))
-			return false, nil
+			return false, fmt.Errorf("list pods error: %w", err)
 		}
 		if len(list.Items) != 5 {
-			t.Log(fmt.Errorf("want 5 pods, got %d", len(list.Items)))
-			return false, nil
+			return false, fmt.Errorf("want 5 pods, got %d", len(list.Items))
 		}
 
 		for index, pod := range list.Items {
 			if nodeInfo, ok := nodeGetFunc(pod.Spec.NodeName); ok {
 				if pod.Status.Phase != corev1.PodRunning {
-					t.Log(fmt.Errorf("want pod %s phase is running, got %s", pod.Name, pod.Status.Phase))
-					return false, nil
+					return false, fmt.Errorf("want pod %s phase is running, got %s", pod.Name, pod.Status.Phase)
 				}
 				if pods.needLockPod(&list.Items[index]) {
 					if pod.Status.HostIP != nodeInfo.HostIPs[0] {
-						t.Log(fmt.Errorf("want pod %s hostIP=%s, got %s", pod.Name, nodeInfo.HostIPs[0], pod.Status.HostIP))
-						return false, nil
+						return false, fmt.Errorf("want pod %s hostIP=%s, got %s", pod.Name, nodeInfo.HostIPs[0], pod.Status.HostIP)
 					}
 					if pod.Spec.HostNetwork {
 						if pod.Status.PodIP != nodeInfo.HostIPs[0] {
-							t.Fatal(fmt.Errorf("want pod %s podIP=%s, got %s", pod.Name, nodeInfo.HostIPs[0], pod.Status.PodIP))
-							return false, nil
+							return false, fmt.Errorf("want pod %s podIP=%s, got %s", pod.Name, nodeInfo.HostIPs[0], pod.Status.PodIP)
 						}
 					} else {
 						cidr, _ := parseCIDR(nodeInfo.PodCIDRs[0])
 						if !cidr.Contains(net.ParseIP(pod.Status.PodIP)) {
-							t.Log(fmt.Errorf("want pod %s podIP=%s in %s, got not", pod.Name, pod.Status.PodIP, nodeInfo.PodCIDRs[0]))
-							return false, nil
+							return false, fmt.Errorf("want pod %s podIP=%s in %s, got not", pod.Name, pod.Status.PodIP, nodeInfo.PodCIDRs[0])
 						}
 					}
 				}
 			} else if pod.Status.Phase == corev1.PodRunning {
-				t.Log(fmt.Errorf("want pod %s phase is not running, got %s", pod.Name, pod.Status.Phase))
-				return false, nil
+				return false, fmt.Errorf("want pod %s phase is not running, got %s", pod.Name, pod.Status.Phase)
 			}
 		}
 		return true, nil
@@ -313,7 +306,7 @@ func TestPodController(t *testing.T) {
 		t.Fatal(fmt.Errorf("delete pod error: %w", err))
 	}
 
-	err = wait.PollUntilWithContext(ctx, time.Second, func(ctx context.Context) (done bool, err error) {
+	err = wait.Poll(ctx, func(ctx context.Context) (done bool, err error) {
 		list, err = clientset.CoreV1().Pods("default").List(ctx, metav1.ListOptions{})
 		if err != nil {
 			return false, fmt.Errorf("list pods error: %w", err)