...

Source file src/k8s.io/kubectl/pkg/cmd/wait/wait_test.go

Documentation: k8s.io/kubectl/pkg/cmd/wait

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package wait
    18  
    19  import (
    20  	"io"
    21  	"strings"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/stretchr/testify/require"
    26  
    27  	"k8s.io/apimachinery/pkg/api/meta"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/apimachinery/pkg/runtime/schema"
    32  	"k8s.io/apimachinery/pkg/types"
    33  	"k8s.io/apimachinery/pkg/util/yaml"
    34  	"k8s.io/apimachinery/pkg/watch"
    35  	"k8s.io/cli-runtime/pkg/genericclioptions"
    36  	"k8s.io/cli-runtime/pkg/genericiooptions"
    37  	"k8s.io/cli-runtime/pkg/printers"
    38  	"k8s.io/cli-runtime/pkg/resource"
    39  	dynamicfakeclient "k8s.io/client-go/dynamic/fake"
    40  	clienttesting "k8s.io/client-go/testing"
    41  )
    42  
    43  const (
    44  	None    string = ""
    45  	podYAML string = `
    46  apiVersion: v1
    47  kind: Pod
    48  metadata:
    49      creationTimestamp: "1998-10-21T18:39:43Z"
    50      generateName: foo-b6699dcfb-
    51      labels:
    52        app: nginx
    53        pod-template-hash: b6699dcfb
    54      name: foo-b6699dcfb-rnv7t
    55      namespace: default
    56      ownerReferences:
    57      - apiVersion: apps/v1
    58        blockOwnerDeletion: true
    59        controller: true
    60        kind: ReplicaSet
    61        name: foo-b6699dcfb
    62        uid: 8fc1088c-15d5-4a8c-8502-4dfcedef97b8
    63      resourceVersion: "14203463"
    64      uid: e2cc99fa-5a28-44da-b880-4dded28882ef
    65  spec:
    66      containers:
    67      - image: nginx
    68        imagePullPolicy: IfNotPresent
    69        name: nginx
    70        ports:
    71        - containerPort: 80
    72        protocol: TCP
    73        resources:
    74          limits:
    75            cpu: 500m
    76            memory: 128Mi
    77          requests:
    78            cpu: 250m
    79            memory: 64Mi  
    80        terminationMessagePath: /dev/termination-log
    81        terminationMessagePolicy: File
    82        volumeMounts:
    83        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
    84          name: kube-api-access-s64k4
    85          readOnly: true
    86      dnsPolicy: ClusterFirst
    87      enableServiceLinks: true
    88      nodeName: knode0
    89      preemptionPolicy: PreemptLowerPriority
    90      priority: 0
    91      restartPolicy: Always
    92      schedulerName: default-scheduler
    93      securityContext: {}
    94      serviceAccount: default
    95      serviceAccountName: default
    96      terminationGracePeriodSeconds: 30
    97      tolerations:
    98      - effect: NoExecute
    99        key: node.kubernetes.io/not-ready
   100        operator: Exists
   101        tolerationSeconds: 300
   102      - effect: NoExecute
   103        key: node.kubernetes.io/unreachable
   104        operator: Exists
   105        tolerationSeconds: 300
   106      volumes:
   107      - name: kube-api-access-s64k4
   108        projected:
   109          defaultMode: 420
   110          sources:
   111          - serviceAccountToken:
   112              expirationSeconds: 3607
   113              path: token
   114          - configMap:
   115              items:
   116              - key: ca.crt
   117                path: ca.crt
   118              name: kube-root-ca.crt
   119  status:
   120      conditions:
   121      - lastProbeTime: null
   122        lastTransitionTime: "1998-10-21T18:39:37Z"
   123        status: "True"
   124        type: Initialized
   125      - lastProbeTime: null
   126        lastTransitionTime: "1998-10-21T18:39:42Z"
   127        status: "True"
   128        type: Ready
   129      - lastProbeTime: null
   130        lastTransitionTime: "1998-10-21T18:39:42Z"
   131        status: "True"
   132        type: ContainersReady
   133      - lastProbeTime: null
   134        lastTransitionTime: "1998-10-21T18:39:37Z"
   135        status: "True"
   136        type: PodScheduled
   137      containerStatuses:
   138      - containerID: containerd://e35792ba1d6e9a56629659b35dbdb93dacaa0a413962ee04775319f5438e493c
   139        image: docker.io/library/nginx:latest
   140        imageID: docker.io/library/nginx@sha256:644a70516a26004c97d0d85c7fe1d0c3a67ea8ab7ddf4aff193d9f301670cf36
   141        lastState: {}
   142        name: nginx
   143        ready: true
   144        restartCount: 0
   145        started: true
   146        state:
   147          running:
   148            startedAt: "1998-10-21T18:39:41Z"
   149      hostIP: 192.168.0.22
   150      phase: Running
   151      podIP: 10.42.1.203
   152      podIPs:
   153      - ip: 10.42.1.203
   154      qosClass: Burstable
   155      startTime: "1998-10-21T18:39:37Z"
   156  `
   157  )
   158  
   159  func newUnstructuredList(items ...*unstructured.Unstructured) *unstructured.UnstructuredList {
   160  	list := &unstructured.UnstructuredList{}
   161  	for i := range items {
   162  		list.Items = append(list.Items, *items[i])
   163  	}
   164  	return list
   165  }
   166  
   167  func newUnstructured(apiVersion, kind, namespace, name string) *unstructured.Unstructured {
   168  	return &unstructured.Unstructured{
   169  		Object: map[string]interface{}{
   170  			"apiVersion": apiVersion,
   171  			"kind":       kind,
   172  			"metadata": map[string]interface{}{
   173  				"namespace": namespace,
   174  				"name":      name,
   175  				"uid":       "some-UID-value",
   176  			},
   177  		},
   178  	}
   179  }
   180  
   181  func newUnstructuredWithGeneration(apiVersion, kind, namespace, name string, generation int64) *unstructured.Unstructured {
   182  	return &unstructured.Unstructured{
   183  		Object: map[string]interface{}{
   184  			"apiVersion": apiVersion,
   185  			"kind":       kind,
   186  			"metadata": map[string]interface{}{
   187  				"namespace":  namespace,
   188  				"name":       name,
   189  				"uid":        "some-UID-value",
   190  				"generation": generation,
   191  			},
   192  		},
   193  	}
   194  }
   195  
   196  func newUnstructuredStatus(status *metav1.Status) runtime.Unstructured {
   197  	obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(status)
   198  	if err != nil {
   199  		panic(err)
   200  	}
   201  	return &unstructured.Unstructured{
   202  		Object: obj,
   203  	}
   204  }
   205  
   206  func addCondition(in *unstructured.Unstructured, name, status string) *unstructured.Unstructured {
   207  	conditions, _, _ := unstructured.NestedSlice(in.Object, "status", "conditions")
   208  	conditions = append(conditions, map[string]interface{}{
   209  		"type":   name,
   210  		"status": status,
   211  	})
   212  	unstructured.SetNestedSlice(in.Object, conditions, "status", "conditions")
   213  	return in
   214  }
   215  
   216  func addConditionWithObservedGeneration(in *unstructured.Unstructured, name, status string, observedGeneration int64) *unstructured.Unstructured {
   217  	conditions, _, _ := unstructured.NestedSlice(in.Object, "status", "conditions")
   218  	conditions = append(conditions, map[string]interface{}{
   219  		"type":               name,
   220  		"status":             status,
   221  		"observedGeneration": observedGeneration,
   222  	})
   223  	unstructured.SetNestedSlice(in.Object, conditions, "status", "conditions")
   224  	return in
   225  }
   226  
   227  // createUnstructured parses the yaml string into a map[string]interface{}.  Verifies that the string does not have
   228  // any tab characters.
   229  func createUnstructured(t *testing.T, config string) *unstructured.Unstructured {
   230  	t.Helper()
   231  	result := map[string]interface{}{}
   232  
   233  	require.False(t, strings.Contains(config, "\t"), "Yaml %s cannot contain tabs", config)
   234  	require.NoError(t, yaml.Unmarshal([]byte(config), &result), "Could not parse config:\n\n%s\n", config)
   235  
   236  	return &unstructured.Unstructured{
   237  		Object: result,
   238  	}
   239  }
   240  
   241  func TestWaitForDeletion(t *testing.T) {
   242  	scheme := runtime.NewScheme()
   243  	listMapping := map[schema.GroupVersionResource]string{
   244  		{Group: "group", Version: "version", Resource: "theresource"}:   "TheKindList",
   245  		{Group: "group", Version: "version", Resource: "theresource-1"}: "TheKindList",
   246  		{Group: "group", Version: "version", Resource: "theresource-2"}: "TheKindList",
   247  	}
   248  
   249  	tests := []struct {
   250  		name       string
   251  		infos      []*resource.Info
   252  		fakeClient func() *dynamicfakeclient.FakeDynamicClient
   253  		timeout    time.Duration
   254  		uidMap     UIDMap
   255  
   256  		expectedErr string
   257  	}{
   258  		{
   259  			name: "missing on get",
   260  			infos: []*resource.Info{
   261  				{
   262  					Mapping: &meta.RESTMapping{
   263  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   264  					},
   265  					Name:      "name-foo",
   266  					Namespace: "ns-foo",
   267  				},
   268  			},
   269  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   270  				return dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   271  			},
   272  			timeout: 10 * time.Second,
   273  		},
   274  		{
   275  			name:  "handles no infos",
   276  			infos: []*resource.Info{},
   277  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   278  				return dynamicfakeclient.NewSimpleDynamicClient(scheme)
   279  			},
   280  			timeout:     10 * time.Second,
   281  			expectedErr: errNoMatchingResources.Error(),
   282  		},
   283  		{
   284  			name: "uid conflict on get",
   285  			infos: []*resource.Info{
   286  				{
   287  					Mapping: &meta.RESTMapping{
   288  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   289  					},
   290  					Name:      "name-foo",
   291  					Namespace: "ns-foo",
   292  				},
   293  			},
   294  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   295  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   296  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   297  					return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
   298  				})
   299  				count := 0
   300  				fakeClient.PrependWatchReactor("theresource", func(action clienttesting.Action) (handled bool, ret watch.Interface, err error) {
   301  					if count == 0 {
   302  						count++
   303  						fakeWatch := watch.NewRaceFreeFake()
   304  						go func() {
   305  							time.Sleep(1 * time.Second)
   306  							fakeWatch.Stop()
   307  						}()
   308  						return true, fakeWatch, nil
   309  					}
   310  					fakeWatch := watch.NewRaceFreeFake()
   311  					return true, fakeWatch, nil
   312  				})
   313  				return fakeClient
   314  			},
   315  			timeout: 10 * time.Second,
   316  			uidMap: UIDMap{
   317  				ResourceLocation{Namespace: "ns-foo", Name: "name-foo"}:                                                                               types.UID("some-UID-value"),
   318  				ResourceLocation{GroupResource: schema.GroupResource{Group: "group", Resource: "theresource"}, Namespace: "ns-foo", Name: "name-foo"}: types.UID("some-nonmatching-UID-value"),
   319  			},
   320  		},
   321  		{
   322  			name: "times out",
   323  			infos: []*resource.Info{
   324  				{
   325  					Mapping: &meta.RESTMapping{
   326  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   327  					},
   328  					Name:      "name-foo",
   329  					Namespace: "ns-foo",
   330  				},
   331  			},
   332  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   333  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   334  				fakeClient.PrependReactor("get", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   335  					return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
   336  				})
   337  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   338  					return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
   339  				})
   340  				return fakeClient
   341  			},
   342  			timeout: 1 * time.Second,
   343  
   344  			expectedErr: "timed out waiting for the condition on theresource/name-foo",
   345  		},
   346  		{
   347  			name: "delete for existing resource with no timeout",
   348  			infos: []*resource.Info{
   349  				{
   350  					Mapping: &meta.RESTMapping{
   351  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   352  					},
   353  					Name:      "name-foo",
   354  					Namespace: "ns-foo",
   355  				},
   356  			},
   357  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   358  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   359  				fakeClient.PrependReactor("get", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   360  					return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
   361  				})
   362  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   363  					return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
   364  				})
   365  				return fakeClient
   366  			},
   367  			timeout: 0 * time.Second,
   368  
   369  			expectedErr: "condition not met for theresource/name-foo",
   370  		},
   371  		{
   372  			name: "delete for nonexisting resource with no timeout",
   373  			infos: []*resource.Info{
   374  				{
   375  					Mapping: &meta.RESTMapping{
   376  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "thenonexistentresource"},
   377  					},
   378  					Name:      "name-foo",
   379  					Namespace: "ns-foo",
   380  				},
   381  			},
   382  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   383  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   384  				fakeClient.PrependReactor("get", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   385  					return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
   386  				})
   387  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   388  					return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
   389  				})
   390  				return fakeClient
   391  			},
   392  			timeout: 0 * time.Second,
   393  
   394  			expectedErr: "",
   395  		},
   396  		{
   397  			name: "handles watch close out",
   398  			infos: []*resource.Info{
   399  				{
   400  					Mapping: &meta.RESTMapping{
   401  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   402  					},
   403  					Name:      "name-foo",
   404  					Namespace: "ns-foo",
   405  				},
   406  			},
   407  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   408  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   409  				fakeClient.PrependReactor("get", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   410  					unstructuredObj := newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")
   411  					unstructuredObj.SetResourceVersion("123")
   412  					unstructuredList := newUnstructuredList(unstructuredObj)
   413  					unstructuredList.SetResourceVersion("234")
   414  					return true, unstructuredList, nil
   415  				})
   416  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   417  					unstructuredObj := newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")
   418  					unstructuredObj.SetResourceVersion("123")
   419  					unstructuredList := newUnstructuredList(unstructuredObj)
   420  					unstructuredList.SetResourceVersion("234")
   421  					return true, unstructuredList, nil
   422  				})
   423  				count := 0
   424  				fakeClient.PrependWatchReactor("theresource", func(action clienttesting.Action) (handled bool, ret watch.Interface, err error) {
   425  					if count == 0 {
   426  						count++
   427  						fakeWatch := watch.NewRaceFreeFake()
   428  						go func() {
   429  							time.Sleep(1 * time.Second)
   430  							fakeWatch.Stop()
   431  						}()
   432  						return true, fakeWatch, nil
   433  					}
   434  					fakeWatch := watch.NewRaceFreeFake()
   435  					return true, fakeWatch, nil
   436  				})
   437  				return fakeClient
   438  			},
   439  			timeout: 3 * time.Second,
   440  
   441  			expectedErr: "timed out waiting for the condition on theresource/name-foo",
   442  		},
   443  		{
   444  			name: "handles watch delete",
   445  			infos: []*resource.Info{
   446  				{
   447  					Mapping: &meta.RESTMapping{
   448  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   449  					},
   450  					Name:      "name-foo",
   451  					Namespace: "ns-foo",
   452  				},
   453  			},
   454  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   455  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   456  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   457  					return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
   458  				})
   459  				fakeClient.PrependWatchReactor("theresource", func(action clienttesting.Action) (handled bool, ret watch.Interface, err error) {
   460  					fakeWatch := watch.NewRaceFreeFake()
   461  					fakeWatch.Action(watch.Deleted, newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"))
   462  					return true, fakeWatch, nil
   463  				})
   464  				return fakeClient
   465  			},
   466  			timeout: 10 * time.Second,
   467  		},
   468  		{
   469  			name: "handles watch delete multiple",
   470  			infos: []*resource.Info{
   471  				{
   472  					Mapping: &meta.RESTMapping{
   473  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource-1"},
   474  					},
   475  					Name:      "name-foo-1",
   476  					Namespace: "ns-foo",
   477  				},
   478  				{
   479  					Mapping: &meta.RESTMapping{
   480  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource-2"},
   481  					},
   482  					Name:      "name-foo-2",
   483  					Namespace: "ns-foo",
   484  				},
   485  			},
   486  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   487  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   488  				fakeClient.PrependReactor("get", "theresource-1", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   489  					return true, newUnstructured("group/version", "TheKind", "ns-foo", "name-foo-1"), nil
   490  				})
   491  				fakeClient.PrependReactor("get", "theresource-2", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   492  					return true, newUnstructured("group/version", "TheKind", "ns-foo", "name-foo-2"), nil
   493  				})
   494  				fakeClient.PrependWatchReactor("theresource-1", func(action clienttesting.Action) (handled bool, ret watch.Interface, err error) {
   495  					fakeWatch := watch.NewRaceFreeFake()
   496  					fakeWatch.Action(watch.Deleted, newUnstructured("group/version", "TheKind", "ns-foo", "name-foo-1"))
   497  					return true, fakeWatch, nil
   498  				})
   499  				fakeClient.PrependWatchReactor("theresource-2", func(action clienttesting.Action) (handled bool, ret watch.Interface, err error) {
   500  					fakeWatch := watch.NewRaceFreeFake()
   501  					fakeWatch.Action(watch.Deleted, newUnstructured("group/version", "TheKind", "ns-foo", "name-foo-2"))
   502  					return true, fakeWatch, nil
   503  				})
   504  				return fakeClient
   505  			},
   506  			timeout: 10 * time.Second,
   507  		},
   508  		{
   509  			name: "ignores watch error",
   510  			infos: []*resource.Info{
   511  				{
   512  					Mapping: &meta.RESTMapping{
   513  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   514  					},
   515  					Name:      "name-foo",
   516  					Namespace: "ns-foo",
   517  				},
   518  			},
   519  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   520  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   521  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   522  					return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
   523  				})
   524  				count := 0
   525  				fakeClient.PrependWatchReactor("theresource", func(action clienttesting.Action) (handled bool, ret watch.Interface, err error) {
   526  					fakeWatch := watch.NewRaceFreeFake()
   527  					if count == 0 {
   528  						fakeWatch.Error(newUnstructuredStatus(&metav1.Status{
   529  							TypeMeta: metav1.TypeMeta{Kind: "Status", APIVersion: "v1"},
   530  							Status:   "Failure",
   531  							Code:     500,
   532  							Message:  "Bad",
   533  						}))
   534  						fakeWatch.Stop()
   535  					} else {
   536  						fakeWatch.Action(watch.Deleted, newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"))
   537  					}
   538  					count++
   539  					return true, fakeWatch, nil
   540  				})
   541  				return fakeClient
   542  			},
   543  			timeout: 10 * time.Second,
   544  		},
   545  	}
   546  
   547  	for _, test := range tests {
   548  		t.Run(test.name, func(t *testing.T) {
   549  			fakeClient := test.fakeClient()
   550  			o := &WaitOptions{
   551  				ResourceFinder: genericclioptions.NewSimpleFakeResourceFinder(test.infos...),
   552  				UIDMap:         test.uidMap,
   553  				DynamicClient:  fakeClient,
   554  				Timeout:        test.timeout,
   555  
   556  				Printer:     printers.NewDiscardingPrinter(),
   557  				ConditionFn: IsDeleted,
   558  				IOStreams:   genericiooptions.NewTestIOStreamsDiscard(),
   559  			}
   560  			err := o.RunWait()
   561  			switch {
   562  			case err == nil && len(test.expectedErr) == 0:
   563  			case err != nil && len(test.expectedErr) == 0:
   564  				t.Fatal(err)
   565  			case err == nil && len(test.expectedErr) != 0:
   566  				t.Fatalf("missing: %q", test.expectedErr)
   567  			case err != nil && len(test.expectedErr) != 0:
   568  				if !strings.Contains(err.Error(), test.expectedErr) {
   569  					t.Fatalf("expected %q, got %q", test.expectedErr, err.Error())
   570  				}
   571  			}
   572  		})
   573  	}
   574  }
   575  
   576  func TestWaitForCondition(t *testing.T) {
   577  	scheme := runtime.NewScheme()
   578  	listMapping := map[schema.GroupVersionResource]string{
   579  		{Group: "group", Version: "version", Resource: "theresource"}: "TheKindList",
   580  	}
   581  
   582  	tests := []struct {
   583  		name       string
   584  		infos      []*resource.Info
   585  		fakeClient func() *dynamicfakeclient.FakeDynamicClient
   586  		timeout    time.Duration
   587  
   588  		expectedErr string
   589  	}{
   590  		{
   591  			name: "present on get",
   592  			infos: []*resource.Info{
   593  				{
   594  					Mapping: &meta.RESTMapping{
   595  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   596  					},
   597  					Name:      "name-foo",
   598  					Namespace: "ns-foo",
   599  				},
   600  			},
   601  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   602  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   603  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   604  					return true, newUnstructuredList(addCondition(
   605  						newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
   606  						"the-condition", "status-value",
   607  					)), nil
   608  				})
   609  				return fakeClient
   610  			},
   611  			timeout: 10 * time.Second,
   612  		},
   613  		{
   614  			name:  "handles no infos",
   615  			infos: []*resource.Info{},
   616  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   617  				return dynamicfakeclient.NewSimpleDynamicClient(scheme)
   618  			},
   619  			timeout:     10 * time.Second,
   620  			expectedErr: errNoMatchingResources.Error(),
   621  		},
   622  		{
   623  			name: "handles empty object name",
   624  			infos: []*resource.Info{
   625  				{
   626  					Mapping: &meta.RESTMapping{
   627  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   628  					},
   629  					Namespace: "ns-foo",
   630  				},
   631  			},
   632  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   633  				return dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   634  			},
   635  			timeout:     10 * time.Second,
   636  			expectedErr: "resource name must be provided",
   637  		},
   638  		{
   639  			name: "times out",
   640  			infos: []*resource.Info{
   641  				{
   642  					Mapping: &meta.RESTMapping{
   643  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   644  					},
   645  					Name:      "name-foo",
   646  					Namespace: "ns-foo",
   647  				},
   648  			},
   649  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   650  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   651  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   652  					return true, addCondition(
   653  						newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
   654  						"some-other-condition", "status-value",
   655  					), nil
   656  				})
   657  				return fakeClient
   658  			},
   659  			timeout: 1 * time.Second,
   660  
   661  			expectedErr: `theresource.group "name-foo" not found`,
   662  		},
   663  		{
   664  			name: "for nonexisting resource with no timeout",
   665  			infos: []*resource.Info{
   666  				{
   667  					Mapping: &meta.RESTMapping{
   668  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "thenonexistingresource"},
   669  					},
   670  					Name:      "name-foo",
   671  					Namespace: "ns-foo",
   672  				},
   673  			},
   674  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   675  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   676  				fakeClient.PrependReactor("get", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   677  					return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
   678  				})
   679  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   680  					return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
   681  				})
   682  				return fakeClient
   683  			},
   684  			timeout: 0 * time.Second,
   685  
   686  			expectedErr: "thenonexistingresource.group \"name-foo\" not found",
   687  		},
   688  		{
   689  			name: "for existing resource with no timeout",
   690  			infos: []*resource.Info{
   691  				{
   692  					Mapping: &meta.RESTMapping{
   693  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   694  					},
   695  					Name:      "name-foo",
   696  					Namespace: "ns-foo",
   697  				},
   698  			},
   699  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   700  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   701  				fakeClient.PrependReactor("get", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   702  					return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
   703  				})
   704  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   705  					return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
   706  				})
   707  				return fakeClient
   708  			},
   709  			timeout: 0 * time.Second,
   710  
   711  			expectedErr: "condition not met for theresource/name-foo",
   712  		},
   713  		{
   714  			name: "handles watch close out",
   715  			infos: []*resource.Info{
   716  				{
   717  					Mapping: &meta.RESTMapping{
   718  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   719  					},
   720  					Name:      "name-foo",
   721  					Namespace: "ns-foo",
   722  				},
   723  			},
   724  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   725  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   726  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   727  					unstructuredObj := newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")
   728  					unstructuredObj.SetResourceVersion("123")
   729  					unstructuredList := newUnstructuredList(unstructuredObj)
   730  					unstructuredList.SetResourceVersion("234")
   731  					return true, unstructuredList, nil
   732  				})
   733  				count := 0
   734  				fakeClient.PrependWatchReactor("theresource", func(action clienttesting.Action) (handled bool, ret watch.Interface, err error) {
   735  					if count == 0 {
   736  						count++
   737  						fakeWatch := watch.NewRaceFreeFake()
   738  						go func() {
   739  							time.Sleep(1 * time.Second)
   740  							fakeWatch.Stop()
   741  						}()
   742  						return true, fakeWatch, nil
   743  					}
   744  					fakeWatch := watch.NewRaceFreeFake()
   745  					return true, fakeWatch, nil
   746  				})
   747  				return fakeClient
   748  			},
   749  			timeout: 3 * time.Second,
   750  
   751  			expectedErr: "timed out waiting for the condition on theresource/name-foo",
   752  		},
   753  		{
   754  			name: "handles watch condition change",
   755  			infos: []*resource.Info{
   756  				{
   757  					Mapping: &meta.RESTMapping{
   758  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   759  					},
   760  					Name:      "name-foo",
   761  					Namespace: "ns-foo",
   762  				},
   763  			},
   764  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   765  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   766  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   767  					return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
   768  				})
   769  				fakeClient.PrependWatchReactor("theresource", func(action clienttesting.Action) (handled bool, ret watch.Interface, err error) {
   770  					fakeWatch := watch.NewRaceFreeFake()
   771  					fakeWatch.Action(watch.Modified, addCondition(
   772  						newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
   773  						"the-condition", "status-value",
   774  					))
   775  					return true, fakeWatch, nil
   776  				})
   777  				return fakeClient
   778  			},
   779  			timeout: 10 * time.Second,
   780  		},
   781  		{
   782  			name: "handles watch created",
   783  			infos: []*resource.Info{
   784  				{
   785  					Mapping: &meta.RESTMapping{
   786  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   787  					},
   788  					Name:      "name-foo",
   789  					Namespace: "ns-foo",
   790  				},
   791  			},
   792  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   793  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   794  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   795  					return true, newUnstructuredList(addCondition(
   796  						newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
   797  						"the-condition", "status-value",
   798  					)), nil
   799  				})
   800  				return fakeClient
   801  			},
   802  			timeout: 1 * time.Second,
   803  		},
   804  		{
   805  			name: "ignores watch error",
   806  			infos: []*resource.Info{
   807  				{
   808  					Mapping: &meta.RESTMapping{
   809  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   810  					},
   811  					Name:      "name-foo",
   812  					Namespace: "ns-foo",
   813  				},
   814  			},
   815  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   816  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   817  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   818  					return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
   819  				})
   820  				count := 0
   821  				fakeClient.PrependWatchReactor("theresource", func(action clienttesting.Action) (handled bool, ret watch.Interface, err error) {
   822  					fakeWatch := watch.NewRaceFreeFake()
   823  					if count == 0 {
   824  						fakeWatch.Error(newUnstructuredStatus(&metav1.Status{
   825  							TypeMeta: metav1.TypeMeta{Kind: "Status", APIVersion: "v1"},
   826  							Status:   "Failure",
   827  							Code:     500,
   828  							Message:  "Bad",
   829  						}))
   830  						fakeWatch.Stop()
   831  					} else {
   832  						fakeWatch.Action(watch.Modified, addCondition(
   833  							newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
   834  							"the-condition", "status-value",
   835  						))
   836  					}
   837  					count++
   838  					return true, fakeWatch, nil
   839  				})
   840  				return fakeClient
   841  			},
   842  			timeout: 10 * time.Second,
   843  		},
   844  		{
   845  			name: "times out due to stale .status.conditions[0].observedGeneration",
   846  			infos: []*resource.Info{
   847  				{
   848  					Mapping: &meta.RESTMapping{
   849  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   850  					},
   851  					Name:      "name-foo",
   852  					Namespace: "ns-foo",
   853  				},
   854  			},
   855  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   856  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   857  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   858  					return true, addConditionWithObservedGeneration(
   859  						newUnstructuredWithGeneration("group/version", "TheKind", "ns-foo", "name-foo", 2),
   860  						"the-condition", "status-value", 1,
   861  					), nil
   862  				})
   863  				return fakeClient
   864  			},
   865  			timeout: 1 * time.Second,
   866  
   867  			expectedErr: `theresource.group "name-foo" not found`,
   868  		},
   869  		{
   870  			name: "handles watch .status.conditions[0].observedGeneration change",
   871  			infos: []*resource.Info{
   872  				{
   873  					Mapping: &meta.RESTMapping{
   874  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   875  					},
   876  					Name:      "name-foo",
   877  					Namespace: "ns-foo",
   878  				},
   879  			},
   880  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   881  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   882  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   883  					return true, newUnstructuredList(addConditionWithObservedGeneration(newUnstructuredWithGeneration("group/version", "TheKind", "ns-foo", "name-foo", 2), "the-condition", "status-value", 1)), nil
   884  				})
   885  				fakeClient.PrependWatchReactor("theresource", func(action clienttesting.Action) (handled bool, ret watch.Interface, err error) {
   886  					fakeWatch := watch.NewRaceFreeFake()
   887  					fakeWatch.Action(watch.Modified, addConditionWithObservedGeneration(
   888  						newUnstructuredWithGeneration("group/version", "TheKind", "ns-foo", "name-foo", 2),
   889  						"the-condition", "status-value", 2,
   890  					))
   891  					return true, fakeWatch, nil
   892  				})
   893  				return fakeClient
   894  			},
   895  			timeout: 10 * time.Second,
   896  		},
   897  		{
   898  			name: "times out due to stale .status.observedGeneration",
   899  			infos: []*resource.Info{
   900  				{
   901  					Mapping: &meta.RESTMapping{
   902  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   903  					},
   904  					Name:      "name-foo",
   905  					Namespace: "ns-foo",
   906  				},
   907  			},
   908  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   909  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   910  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   911  					instance := addCondition(
   912  						newUnstructuredWithGeneration("group/version", "TheKind", "ns-foo", "name-foo", 2),
   913  						"the-condition", "status-value")
   914  					unstructured.SetNestedField(instance.Object, int64(1), "status", "observedGeneration")
   915  					return true, instance, nil
   916  				})
   917  				return fakeClient
   918  			},
   919  			timeout: 1 * time.Second,
   920  
   921  			expectedErr: `theresource.group "name-foo" not found`,
   922  		},
   923  		{
   924  			name: "handles watch .status.observedGeneration change",
   925  			infos: []*resource.Info{
   926  				{
   927  					Mapping: &meta.RESTMapping{
   928  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
   929  					},
   930  					Name:      "name-foo",
   931  					Namespace: "ns-foo",
   932  				},
   933  			},
   934  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
   935  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   936  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   937  					instance := addCondition(
   938  						newUnstructuredWithGeneration("group/version", "TheKind", "ns-foo", "name-foo", 2),
   939  						"the-condition", "status-value")
   940  					unstructured.SetNestedField(instance.Object, int64(1), "status", "observedGeneration")
   941  					return true, newUnstructuredList(instance), nil
   942  				})
   943  				fakeClient.PrependWatchReactor("theresource", func(action clienttesting.Action) (handled bool, ret watch.Interface, err error) {
   944  					instance := addCondition(
   945  						newUnstructuredWithGeneration("group/version", "TheKind", "ns-foo", "name-foo", 2),
   946  						"the-condition", "status-value")
   947  					unstructured.SetNestedField(instance.Object, int64(2), "status", "observedGeneration")
   948  					fakeWatch := watch.NewRaceFreeFake()
   949  					fakeWatch.Action(watch.Modified, instance)
   950  					return true, fakeWatch, nil
   951  				})
   952  				return fakeClient
   953  			},
   954  			timeout: 10 * time.Second,
   955  		},
   956  	}
   957  
   958  	for _, test := range tests {
   959  		t.Run(test.name, func(t *testing.T) {
   960  			fakeClient := test.fakeClient()
   961  			o := &WaitOptions{
   962  				ResourceFinder: genericclioptions.NewSimpleFakeResourceFinder(test.infos...),
   963  				DynamicClient:  fakeClient,
   964  				Timeout:        test.timeout,
   965  
   966  				Printer:     printers.NewDiscardingPrinter(),
   967  				ConditionFn: ConditionalWait{conditionName: "the-condition", conditionStatus: "status-value", errOut: io.Discard}.IsConditionMet,
   968  				IOStreams:   genericiooptions.NewTestIOStreamsDiscard(),
   969  			}
   970  			err := o.RunWait()
   971  			switch {
   972  			case err == nil && len(test.expectedErr) == 0:
   973  			case err != nil && len(test.expectedErr) == 0:
   974  				t.Fatal(err)
   975  			case err == nil && len(test.expectedErr) != 0:
   976  				t.Fatalf("missing: %q", test.expectedErr)
   977  			case err != nil && len(test.expectedErr) != 0:
   978  				if !strings.Contains(err.Error(), test.expectedErr) {
   979  					t.Fatalf("expected %q, got %q", test.expectedErr, err.Error())
   980  				}
   981  			}
   982  		})
   983  	}
   984  }
   985  
   986  func TestWaitForDeletionIgnoreNotFound(t *testing.T) {
   987  	scheme := runtime.NewScheme()
   988  	listMapping := map[schema.GroupVersionResource]string{
   989  		{Group: "group", Version: "version", Resource: "theresource"}: "TheKindList",
   990  	}
   991  	infos := []*resource.Info{}
   992  	fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
   993  
   994  	o := &WaitOptions{
   995  		ResourceFinder: genericclioptions.NewSimpleFakeResourceFinder(infos...),
   996  		DynamicClient:  fakeClient,
   997  		Printer:        printers.NewDiscardingPrinter(),
   998  		ConditionFn:    IsDeleted,
   999  		IOStreams:      genericiooptions.NewTestIOStreamsDiscard(),
  1000  		ForCondition:   "delete",
  1001  	}
  1002  	err := o.RunWait()
  1003  	if err != nil {
  1004  		t.Fatalf("unexpected error: %v", err)
  1005  	}
  1006  }
  1007  
  1008  // TestWaitForDifferentJSONPathCondition will run tests on different types of
  1009  // JSONPath expression to check the JSONPath can be parsed correctly from a Pod Yaml
  1010  // and check if the comparison returns as expected.
  1011  func TestWaitForDifferentJSONPathExpression(t *testing.T) {
  1012  	scheme := runtime.NewScheme()
  1013  	listMapping := map[schema.GroupVersionResource]string{
  1014  		{Group: "group", Version: "version", Resource: "theresource"}: "TheKindList",
  1015  	}
  1016  	listReactionfunc := func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
  1017  		return true, newUnstructuredList(createUnstructured(t, podYAML)), nil
  1018  	}
  1019  	infos := []*resource.Info{
  1020  		{
  1021  			Mapping: &meta.RESTMapping{
  1022  				Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
  1023  			},
  1024  			Name:      "foo-b6699dcfb-rnv7t",
  1025  			Namespace: "default",
  1026  		},
  1027  	}
  1028  
  1029  	tests := []struct {
  1030  		name          string
  1031  		fakeClient    func() *dynamicfakeclient.FakeDynamicClient
  1032  		jsonPathExp   string
  1033  		jsonPathValue string
  1034  		matchAnyValue bool
  1035  
  1036  		expectedErr string
  1037  	}{
  1038  		{
  1039  			name: "JSONPath entry not exist",
  1040  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1041  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1042  				fakeClient.PrependReactor("list", "theresource", listReactionfunc)
  1043  				return fakeClient
  1044  			},
  1045  			jsonPathExp:   "{.foo.bar}",
  1046  			jsonPathValue: "baz",
  1047  			matchAnyValue: false,
  1048  
  1049  			expectedErr: "timed out waiting for the condition on theresource/foo-b6699dcfb-rnv7t",
  1050  		},
  1051  		{
  1052  			name: "compare boolean JSONPath entry",
  1053  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1054  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1055  				fakeClient.PrependReactor("list", "theresource", listReactionfunc)
  1056  				return fakeClient
  1057  			},
  1058  			jsonPathExp:   "{.status.containerStatuses[0].ready}",
  1059  			jsonPathValue: "true",
  1060  			matchAnyValue: false,
  1061  
  1062  			expectedErr: None,
  1063  		},
  1064  		{
  1065  			name: "compare boolean JSONPath entry wrong value",
  1066  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1067  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1068  				fakeClient.PrependReactor("list", "theresource", listReactionfunc)
  1069  				return fakeClient
  1070  			},
  1071  			jsonPathExp:   "{.status.containerStatuses[0].ready}",
  1072  			jsonPathValue: "false",
  1073  			matchAnyValue: false,
  1074  
  1075  			expectedErr: "timed out waiting for the condition on theresource/foo-b6699dcfb-rnv7t",
  1076  		},
  1077  		{
  1078  			name: "compare integer JSONPath entry",
  1079  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1080  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1081  				fakeClient.PrependReactor("list", "theresource", listReactionfunc)
  1082  				return fakeClient
  1083  			},
  1084  			jsonPathExp:   "{.spec.containers[0].ports[0].containerPort}",
  1085  			jsonPathValue: "80",
  1086  			matchAnyValue: false,
  1087  
  1088  			expectedErr: None,
  1089  		},
  1090  		{
  1091  			name: "compare integer JSONPath entry wrong value",
  1092  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1093  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1094  				fakeClient.PrependReactor("list", "theresource", listReactionfunc)
  1095  				return fakeClient
  1096  			},
  1097  			jsonPathExp:   "{.spec.containers[0].ports[0].containerPort}",
  1098  			jsonPathValue: "81",
  1099  			matchAnyValue: false,
  1100  
  1101  			expectedErr: "timed out waiting for the condition on theresource/foo-b6699dcfb-rnv7t",
  1102  		},
  1103  		{
  1104  			name: "compare string JSONPath entry",
  1105  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1106  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1107  				fakeClient.PrependReactor("list", "theresource", listReactionfunc)
  1108  				return fakeClient
  1109  			},
  1110  			jsonPathExp:   "{.spec.nodeName}",
  1111  			jsonPathValue: "knode0",
  1112  			matchAnyValue: false,
  1113  
  1114  			expectedErr: None,
  1115  		},
  1116  		{
  1117  			name: "matches literal value of JSONPath entry without value condition",
  1118  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1119  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1120  				fakeClient.PrependReactor("list", "theresource", listReactionfunc)
  1121  				return fakeClient
  1122  			},
  1123  			jsonPathExp:   "{.spec.nodeName}",
  1124  			jsonPathValue: "",
  1125  			matchAnyValue: true,
  1126  
  1127  			expectedErr: None,
  1128  		},
  1129  		{
  1130  			name: "matches complex types map[string]interface{} without value condition",
  1131  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1132  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1133  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
  1134  					return true, newUnstructuredList(createUnstructured(t, podYAML)), nil
  1135  				})
  1136  				return fakeClient
  1137  			},
  1138  			jsonPathExp:   "{.spec}",
  1139  			jsonPathValue: "",
  1140  			matchAnyValue: true,
  1141  
  1142  			expectedErr: None,
  1143  		},
  1144  
  1145  		{
  1146  			name: "compare string JSONPath entry wrong value",
  1147  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1148  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1149  				fakeClient.PrependReactor("list", "theresource", listReactionfunc)
  1150  				return fakeClient
  1151  			},
  1152  			jsonPathExp:   "{.spec.nodeName}",
  1153  			jsonPathValue: "kmaster",
  1154  			matchAnyValue: false,
  1155  
  1156  			expectedErr: "timed out waiting for the condition on theresource/foo-b6699dcfb-rnv7t",
  1157  		},
  1158  		{
  1159  			name: "matches more than one value",
  1160  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1161  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1162  				fakeClient.PrependReactor("list", "theresource", listReactionfunc)
  1163  				return fakeClient
  1164  			},
  1165  			jsonPathExp:   "{.status.conditions[*]}",
  1166  			jsonPathValue: "foo",
  1167  			matchAnyValue: false,
  1168  
  1169  			expectedErr: "given jsonpath expression matches more than one value",
  1170  		},
  1171  		{
  1172  			name: "matches more than one value without value condition",
  1173  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1174  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1175  				fakeClient.PrependReactor("list", "theresource", listReactionfunc)
  1176  				return fakeClient
  1177  			},
  1178  			jsonPathExp:   "{.status.conditions[*]}",
  1179  			jsonPathValue: "",
  1180  			matchAnyValue: true,
  1181  
  1182  			expectedErr: "given jsonpath expression matches more than one value",
  1183  		},
  1184  		{
  1185  			name: "matches more than one list",
  1186  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1187  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1188  				fakeClient.PrependReactor("list", "theresource", listReactionfunc)
  1189  				return fakeClient
  1190  			},
  1191  			jsonPathExp:   "{range .status.conditions[*]}[{.status}] {end}",
  1192  			jsonPathValue: "foo",
  1193  			matchAnyValue: false,
  1194  
  1195  			expectedErr: "given jsonpath expression matches more than one list",
  1196  		},
  1197  		{
  1198  			name: "matches more than one list without value condition",
  1199  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1200  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1201  				fakeClient.PrependReactor("list", "theresource", listReactionfunc)
  1202  				return fakeClient
  1203  			},
  1204  			jsonPathExp:   "{range .status.conditions[*]}[{.status}] {end}",
  1205  			jsonPathValue: "",
  1206  			matchAnyValue: true,
  1207  
  1208  			expectedErr: "given jsonpath expression matches more than one list",
  1209  		},
  1210  		{
  1211  			name: "unsupported type []interface{}",
  1212  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1213  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1214  				fakeClient.PrependReactor("list", "theresource", listReactionfunc)
  1215  				return fakeClient
  1216  			},
  1217  			jsonPathExp:   "{.status.conditions}",
  1218  			jsonPathValue: "True",
  1219  			matchAnyValue: false,
  1220  
  1221  			expectedErr: "jsonpath leads to a nested object or list which is not supported",
  1222  		},
  1223  		{
  1224  			name: "unsupported type map[string]interface{}",
  1225  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1226  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1227  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
  1228  					return true, newUnstructuredList(createUnstructured(t, podYAML)), nil
  1229  				})
  1230  				return fakeClient
  1231  			},
  1232  			jsonPathExp:   "{.spec}",
  1233  			jsonPathValue: "foo",
  1234  			matchAnyValue: false,
  1235  
  1236  			expectedErr: "jsonpath leads to a nested object or list which is not supported",
  1237  		},
  1238  	}
  1239  	for _, test := range tests {
  1240  		t.Run(test.name, func(t *testing.T) {
  1241  			fakeClient := test.fakeClient()
  1242  			j, _ := newJSONPathParser(test.jsonPathExp)
  1243  			o := &WaitOptions{
  1244  				ResourceFinder: genericclioptions.NewSimpleFakeResourceFinder(infos...),
  1245  				DynamicClient:  fakeClient,
  1246  				Timeout:        1 * time.Second,
  1247  
  1248  				Printer: printers.NewDiscardingPrinter(),
  1249  				ConditionFn: JSONPathWait{
  1250  					matchAnyValue:  test.matchAnyValue,
  1251  					jsonPathValue:  test.jsonPathValue,
  1252  					jsonPathParser: j,
  1253  					errOut:         io.Discard}.IsJSONPathConditionMet,
  1254  				IOStreams: genericiooptions.NewTestIOStreamsDiscard(),
  1255  			}
  1256  
  1257  			err := o.RunWait()
  1258  
  1259  			switch {
  1260  			case err == nil && len(test.expectedErr) == 0:
  1261  			case err != nil && len(test.expectedErr) == 0:
  1262  				t.Fatal(err)
  1263  			case err == nil && len(test.expectedErr) != 0:
  1264  				t.Fatalf("missing: %q", test.expectedErr)
  1265  			case err != nil && len(test.expectedErr) != 0:
  1266  				if !strings.Contains(err.Error(), test.expectedErr) {
  1267  					t.Fatalf("expected %q, got %q", test.expectedErr, err.Error())
  1268  				}
  1269  			}
  1270  		})
  1271  	}
  1272  }
  1273  
  1274  // TestWaitForJSONPathCondition will run tests to check whether
  1275  // the List actions and Watch actions match what we expected
  1276  func TestWaitForJSONPathCondition(t *testing.T) {
  1277  	scheme := runtime.NewScheme()
  1278  	listMapping := map[schema.GroupVersionResource]string{
  1279  		{Group: "group", Version: "version", Resource: "theresource"}: "TheKindList",
  1280  	}
  1281  
  1282  	tests := []struct {
  1283  		name          string
  1284  		infos         []*resource.Info
  1285  		fakeClient    func() *dynamicfakeclient.FakeDynamicClient
  1286  		timeout       time.Duration
  1287  		jsonPathExp   string
  1288  		jsonPathValue string
  1289  
  1290  		expectedErr string
  1291  	}{
  1292  		{
  1293  			name: "present on get",
  1294  			infos: []*resource.Info{
  1295  				{
  1296  					Mapping: &meta.RESTMapping{
  1297  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
  1298  					},
  1299  					Name:      "foo-b6699dcfb-rnv7t",
  1300  					Namespace: "default",
  1301  				},
  1302  			},
  1303  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1304  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1305  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
  1306  					return true, newUnstructuredList(
  1307  						createUnstructured(t, podYAML)), nil
  1308  				})
  1309  				return fakeClient
  1310  			},
  1311  			timeout:       3 * time.Second,
  1312  			jsonPathExp:   "{.metadata.name}",
  1313  			jsonPathValue: "foo-b6699dcfb-rnv7t",
  1314  
  1315  			expectedErr: None,
  1316  		},
  1317  		{
  1318  			name:  "handles no infos",
  1319  			infos: []*resource.Info{},
  1320  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1321  				return dynamicfakeclient.NewSimpleDynamicClient(scheme)
  1322  			},
  1323  			timeout:     10 * time.Second,
  1324  			expectedErr: errNoMatchingResources.Error(),
  1325  		},
  1326  		{
  1327  			name: "handles empty object name",
  1328  			infos: []*resource.Info{
  1329  				{
  1330  					Mapping: &meta.RESTMapping{
  1331  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
  1332  					},
  1333  					Namespace: "default",
  1334  				},
  1335  			},
  1336  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1337  				return dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1338  			},
  1339  			timeout: 10 * time.Second,
  1340  
  1341  			expectedErr: "resource name must be provided",
  1342  		},
  1343  		{
  1344  			name: "times out",
  1345  			infos: []*resource.Info{
  1346  				{
  1347  					Mapping: &meta.RESTMapping{
  1348  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
  1349  					},
  1350  					Name:      "foo-b6699dcfb-rnv7t",
  1351  					Namespace: "default",
  1352  				},
  1353  			},
  1354  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1355  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1356  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
  1357  					return true, createUnstructured(t, podYAML), nil
  1358  				})
  1359  				return fakeClient
  1360  			},
  1361  			timeout: 1 * time.Second,
  1362  
  1363  			expectedErr: `theresource.group "foo-b6699dcfb-rnv7t" not found`,
  1364  		},
  1365  		{
  1366  			name: "handles watch close out",
  1367  			infos: []*resource.Info{
  1368  				{
  1369  					Mapping: &meta.RESTMapping{
  1370  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
  1371  					},
  1372  					Name:      "foo-b6699dcfb-rnv7t",
  1373  					Namespace: "default",
  1374  				},
  1375  			},
  1376  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1377  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1378  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
  1379  					unstructuredObj := createUnstructured(t, podYAML)
  1380  					unstructuredObj.SetResourceVersion("123")
  1381  					unstructuredList := newUnstructuredList(unstructuredObj)
  1382  					unstructuredList.SetResourceVersion("234")
  1383  					return true, unstructuredList, nil
  1384  				})
  1385  				count := 0
  1386  				fakeClient.PrependWatchReactor("theresource", func(action clienttesting.Action) (handled bool, ret watch.Interface, err error) {
  1387  					if count == 0 {
  1388  						count++
  1389  						fakeWatch := watch.NewRaceFreeFake()
  1390  						go func() {
  1391  							time.Sleep(1 * time.Second)
  1392  							fakeWatch.Stop()
  1393  						}()
  1394  						return true, fakeWatch, nil
  1395  					}
  1396  					fakeWatch := watch.NewRaceFreeFake()
  1397  					return true, fakeWatch, nil
  1398  				})
  1399  				return fakeClient
  1400  			},
  1401  			timeout:       3 * time.Second,
  1402  			jsonPathExp:   "{.metadata.name}",
  1403  			jsonPathValue: "foo", // use incorrect name so it'll keep waiting
  1404  
  1405  			expectedErr: "timed out waiting for the condition on theresource/foo-b6699dcfb-rnv7t",
  1406  		},
  1407  		{
  1408  			name: "handles watch condition change",
  1409  			infos: []*resource.Info{
  1410  				{
  1411  					Mapping: &meta.RESTMapping{
  1412  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
  1413  					},
  1414  					Name:      "foo-b6699dcfb-rnv7t",
  1415  					Namespace: "default",
  1416  				},
  1417  			},
  1418  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1419  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1420  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
  1421  					unstructuredObj := createUnstructured(t, podYAML)
  1422  					unstructuredObj.SetName("foo")
  1423  					return true, newUnstructuredList(unstructuredObj), nil
  1424  				})
  1425  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
  1426  					unstructuredObj := createUnstructured(t, podYAML)
  1427  					return true, newUnstructuredList(unstructuredObj), nil
  1428  				})
  1429  				return fakeClient
  1430  			},
  1431  			timeout:       10 * time.Second,
  1432  			jsonPathExp:   "{.metadata.name}",
  1433  			jsonPathValue: "foo-b6699dcfb-rnv7t",
  1434  
  1435  			expectedErr: None,
  1436  		},
  1437  		{
  1438  			name: "handles watch created",
  1439  			infos: []*resource.Info{
  1440  				{
  1441  					Mapping: &meta.RESTMapping{
  1442  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
  1443  					},
  1444  					Name:      "foo-b6699dcfb-rnv7t",
  1445  					Namespace: "default",
  1446  				},
  1447  			},
  1448  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1449  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1450  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
  1451  					return true, newUnstructuredList(
  1452  						createUnstructured(t, podYAML)), nil
  1453  				})
  1454  				return fakeClient
  1455  			},
  1456  			timeout:       1 * time.Second,
  1457  			jsonPathExp:   "{.spec.containers[0].image}",
  1458  			jsonPathValue: "nginx",
  1459  
  1460  			expectedErr: None,
  1461  		},
  1462  		{
  1463  			name: "ignores watch error",
  1464  			infos: []*resource.Info{
  1465  				{
  1466  					Mapping: &meta.RESTMapping{
  1467  						Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "theresource"},
  1468  					},
  1469  					Name:      "foo-b6699dcfb-rnv7t",
  1470  					Namespace: "default",
  1471  				},
  1472  			},
  1473  			fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
  1474  				fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
  1475  				fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
  1476  					return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "default", "foo-b6699dcfb-rnv7t")), nil
  1477  				})
  1478  				count := 0
  1479  				fakeClient.PrependWatchReactor("theresource", func(action clienttesting.Action) (handled bool, ret watch.Interface, err error) {
  1480  					fakeWatch := watch.NewRaceFreeFake()
  1481  					if count == 0 {
  1482  						fakeWatch.Error(newUnstructuredStatus(&metav1.Status{
  1483  							TypeMeta: metav1.TypeMeta{Kind: "Status", APIVersion: "v1"},
  1484  							Status:   "Failure",
  1485  							Code:     500,
  1486  							Message:  "Bad",
  1487  						}))
  1488  						fakeWatch.Stop()
  1489  					} else {
  1490  						fakeWatch.Action(watch.Modified, createUnstructured(t, podYAML))
  1491  					}
  1492  					count++
  1493  					return true, fakeWatch, nil
  1494  				})
  1495  				return fakeClient
  1496  			},
  1497  			timeout:       10 * time.Second,
  1498  			jsonPathExp:   "{.metadata.name}",
  1499  			jsonPathValue: "foo-b6699dcfb-rnv7t",
  1500  
  1501  			expectedErr: None,
  1502  		},
  1503  	}
  1504  	for _, test := range tests {
  1505  		t.Run(test.name, func(t *testing.T) {
  1506  			fakeClient := test.fakeClient()
  1507  			j, _ := newJSONPathParser(test.jsonPathExp)
  1508  			o := &WaitOptions{
  1509  				ResourceFinder: genericclioptions.NewSimpleFakeResourceFinder(test.infos...),
  1510  				DynamicClient:  fakeClient,
  1511  				Timeout:        test.timeout,
  1512  
  1513  				Printer: printers.NewDiscardingPrinter(),
  1514  				ConditionFn: JSONPathWait{
  1515  					jsonPathValue:  test.jsonPathValue,
  1516  					jsonPathParser: j, errOut: io.Discard}.IsJSONPathConditionMet,
  1517  				IOStreams: genericiooptions.NewTestIOStreamsDiscard(),
  1518  			}
  1519  
  1520  			err := o.RunWait()
  1521  
  1522  			switch {
  1523  			case err == nil && len(test.expectedErr) == 0:
  1524  			case err != nil && len(test.expectedErr) == 0:
  1525  				t.Fatal(err)
  1526  			case err == nil && len(test.expectedErr) != 0:
  1527  				t.Fatalf("missing: %q", test.expectedErr)
  1528  			case err != nil && len(test.expectedErr) != 0:
  1529  				if !strings.Contains(err.Error(), test.expectedErr) {
  1530  					t.Fatalf("expected %q, got %q", test.expectedErr, err.Error())
  1531  				}
  1532  			}
  1533  		})
  1534  	}
  1535  }
  1536  
  1537  // TestConditionFuncFor tests that the condition string can be properly parsed into a ConditionFunc.
  1538  func TestConditionFuncFor(t *testing.T) {
  1539  	tests := []struct {
  1540  		name        string
  1541  		condition   string
  1542  		expectedErr string
  1543  	}{
  1544  		{
  1545  			name:        "jsonpath missing JSONPath expression",
  1546  			condition:   "jsonpath=",
  1547  			expectedErr: "jsonpath expression cannot be empty",
  1548  		},
  1549  		{
  1550  			name:        "jsonpath check for condition without value",
  1551  			condition:   "jsonpath={.metadata.name}",
  1552  			expectedErr: None,
  1553  		},
  1554  		{
  1555  			name:        "jsonpath check for condition without value relaxed parsing",
  1556  			condition:   "jsonpath=abc",
  1557  			expectedErr: None,
  1558  		},
  1559  		{
  1560  			name:        "jsonpath check for expression and value",
  1561  			condition:   "jsonpath={.metadata.name}=foo-b6699dcfb-rnv7t",
  1562  			expectedErr: None,
  1563  		},
  1564  		{
  1565  			name:        "jsonpath check for expression and value relaxed parsing",
  1566  			condition:   "jsonpath=.metadata.name=foo-b6699dcfb-rnv7t",
  1567  			expectedErr: None,
  1568  		},
  1569  		{
  1570  			name:        "jsonpath selecting based on condition",
  1571  			condition:   `jsonpath={.status.containerStatuses[?(@.name=="foo")].ready}=True`,
  1572  			expectedErr: None,
  1573  		},
  1574  		{
  1575  			name:        "jsonpath selecting based on condition relaxed parsing",
  1576  			condition:   "jsonpath=status.conditions[?(@.type==\"Available\")].status=True",
  1577  			expectedErr: None,
  1578  		},
  1579  		{
  1580  			name:        "jsonpath selecting based on condition without value",
  1581  			condition:   `jsonpath={.status.containerStatuses[?(@.name=="foo")].ready}`,
  1582  			expectedErr: None,
  1583  		},
  1584  		{
  1585  			name:        "jsonpath selecting based on condition without value relaxed parsing",
  1586  			condition:   `jsonpath=.status.containerStatuses[?(@.name=="foo")].ready`,
  1587  			expectedErr: None,
  1588  		},
  1589  		{
  1590  			name:        "jsonpath invalid expression with repeated '='",
  1591  			condition:   "jsonpath={.metadata.name}='test=wrong'",
  1592  			expectedErr: "jsonpath wait format must be --for=jsonpath='{.status.readyReplicas}'=3 or --for=jsonpath='{.status.readyReplicas}'",
  1593  		},
  1594  		{
  1595  			name:        "jsonpath undefined value after '='",
  1596  			condition:   "jsonpath={.metadata.name}=",
  1597  			expectedErr: "jsonpath wait has to have a value after equal sign",
  1598  		},
  1599  		{
  1600  			name:        "jsonpath complex expressions not supported",
  1601  			condition:   "jsonpath={.status.conditions[?(@.type==\"Failed\"||@.type==\"Complete\")].status}=True",
  1602  			expectedErr: "unrecognized character in action: U+007C '|'",
  1603  		},
  1604  		{
  1605  			name:      "jsonpath invalid expression",
  1606  			condition: "jsonpath={=True",
  1607  			expectedErr: "unexpected path string, expected a 'name1.name2' or '.name1.name2' or '{name1.name2}' or " +
  1608  				"'{.name1.name2}'",
  1609  		},
  1610  		{
  1611  			name:        "condition delete",
  1612  			condition:   "delete",
  1613  			expectedErr: None,
  1614  		},
  1615  		{
  1616  			name:        "condition true",
  1617  			condition:   "condition=hello",
  1618  			expectedErr: None,
  1619  		},
  1620  		{
  1621  			name:        "condition with value",
  1622  			condition:   "condition=hello=world",
  1623  			expectedErr: None,
  1624  		},
  1625  		{
  1626  			name:        "unrecognized condition",
  1627  			condition:   "cond=invalid",
  1628  			expectedErr: "unrecognized condition: \"cond=invalid\"",
  1629  		},
  1630  	}
  1631  	for _, test := range tests {
  1632  		t.Run(test.name, func(t *testing.T) {
  1633  			_, err := conditionFuncFor(test.condition, io.Discard)
  1634  			switch {
  1635  			case err == nil && test.expectedErr != None:
  1636  				t.Fatalf("expected error %q, got nil", test.expectedErr)
  1637  			case err != nil && test.expectedErr == None:
  1638  				t.Fatalf("expected no error, got %q", err)
  1639  			case err != nil && test.expectedErr != None:
  1640  				if !strings.Contains(err.Error(), test.expectedErr) {
  1641  					t.Fatalf("expected error %q, got %q", test.expectedErr, err.Error())
  1642  				}
  1643  			}
  1644  		})
  1645  	}
  1646  }
  1647  

View as plain text