github.com/argoproj/argo-cd/v2@v2.10.5/test/e2e/fixture/app/expectation.go (about) 1 package app 2 3 import ( 4 "context" 5 "fmt" 6 "reflect" 7 "regexp" 8 "strings" 9 10 "github.com/argoproj/gitops-engine/pkg/health" 11 . "github.com/argoproj/gitops-engine/pkg/sync/common" 12 v1 "k8s.io/api/core/v1" 13 apierr "k8s.io/apimachinery/pkg/api/errors" 14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 "k8s.io/apimachinery/pkg/fields" 16 17 . "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" 18 "github.com/argoproj/argo-cd/v2/test/e2e/fixture" 19 ) 20 21 type state = string 22 23 const ( 24 failed = "failed" 25 pending = "pending" 26 succeeded = "succeeded" 27 ) 28 29 type Expectation func(c *Consequences) (state state, message string) 30 31 func OperationPhaseIs(expected OperationPhase) Expectation { 32 return func(c *Consequences) (state, string) { 33 operationState := c.app().Status.OperationState 34 actual := OperationRunning 35 if operationState != nil { 36 actual = operationState.Phase 37 } 38 return simple(actual == expected, fmt.Sprintf("operation phase should be %s, is %s", expected, actual)) 39 } 40 } 41 42 func OperationMessageContains(text string) Expectation { 43 return func(c *Consequences) (state, string) { 44 operationState := c.app().Status.OperationState 45 actual := "" 46 if operationState != nil { 47 actual = operationState.Message 48 } 49 return simple(strings.Contains(actual, text), fmt.Sprintf("operation message should contains '%s', got: '%s'", text, actual)) 50 } 51 } 52 53 func simple(success bool, message string) (state, string) { 54 if success { 55 return succeeded, message 56 } else { 57 return pending, message 58 } 59 } 60 61 func SyncStatusIs(expected SyncStatusCode) Expectation { 62 return func(c *Consequences) (state, string) { 63 actual := c.app().Status.Sync.Status 64 return simple(actual == expected, fmt.Sprintf("sync status to be %s, is %s", expected, actual)) 65 } 66 } 67 68 func Condition(conditionType ApplicationConditionType, conditionMessage string) Expectation { 69 return func(c *Consequences) (state, string) { 70 got := c.app().Status.Conditions 71 message := fmt.Sprintf("condition {%s %s} in %v", conditionType, conditionMessage, got) 72 for _, condition := range got { 73 if conditionType == condition.Type && strings.Contains(condition.Message, conditionMessage) { 74 return succeeded, message 75 } 76 } 77 return pending, message 78 } 79 } 80 81 func NoConditions() Expectation { 82 return func(c *Consequences) (state, string) { 83 message := "no conditions" 84 if len(c.app().Status.Conditions) == 0 { 85 return succeeded, message 86 } 87 return pending, message 88 } 89 } 90 91 func NoStatus() Expectation { 92 return func(c *Consequences) (state, string) { 93 message := "no status" 94 if reflect.ValueOf(c.app().Status).IsZero() { 95 return succeeded, message 96 } 97 return pending, message 98 } 99 } 100 101 func StatusExists() Expectation { 102 return func(c *Consequences) (state, string) { 103 message := "status exists" 104 if !reflect.ValueOf(c.app().Status).IsZero() { 105 return succeeded, message 106 } 107 return pending, message 108 } 109 } 110 111 func Namespace(name string, block func(app *Application, ns *v1.Namespace)) Expectation { 112 return func(c *Consequences) (state, string) { 113 ns, err := namespace(name) 114 115 if err != nil { 116 return failed, fmt.Sprintf("namespace not found %s", err.Error()) 117 } 118 119 block(c.app(), ns) 120 return succeeded, fmt.Sprintf("namespace %s assertions passed", name) 121 } 122 } 123 124 func HealthIs(expected health.HealthStatusCode) Expectation { 125 return func(c *Consequences) (state, string) { 126 actual := c.app().Status.Health.Status 127 return simple(actual == expected, fmt.Sprintf("health to should %s, is %s", expected, actual)) 128 } 129 } 130 131 func ResourceSyncStatusIs(kind, resource string, expected SyncStatusCode) Expectation { 132 return func(c *Consequences) (state, string) { 133 actual := c.resource(kind, resource, "").Status 134 return simple(actual == expected, fmt.Sprintf("resource '%s/%s' sync status should be %s, is %s", kind, resource, expected, actual)) 135 } 136 } 137 func ResourceSyncStatusWithNamespaceIs(kind, resource, namespace string, expected SyncStatusCode) Expectation { 138 return func(c *Consequences) (state, string) { 139 actual := c.resource(kind, resource, namespace).Status 140 return simple(actual == expected, fmt.Sprintf("resource '%s/%s' sync status should be %s, is %s", kind, resource, expected, actual)) 141 } 142 } 143 144 func ResourceHealthIs(kind, resource string, expected health.HealthStatusCode) Expectation { 145 return func(c *Consequences) (state, string) { 146 actual := c.resource(kind, resource, "").Health.Status 147 return simple(actual == expected, fmt.Sprintf("resource '%s/%s' health should be %s, is %s", kind, resource, expected, actual)) 148 } 149 } 150 func ResourceHealthWithNamespaceIs(kind, resource, namespace string, expected health.HealthStatusCode) Expectation { 151 return func(c *Consequences) (state, string) { 152 actual := c.resource(kind, resource, namespace).Health.Status 153 return simple(actual == expected, fmt.Sprintf("resource '%s/%s' health should be %s, is %s", kind, resource, expected, actual)) 154 } 155 } 156 func ResourceResultNumbering(num int) Expectation { 157 return func(c *Consequences) (state, string) { 158 actualNum := len(c.app().Status.OperationState.SyncResult.Resources) 159 if actualNum < num { 160 return pending, fmt.Sprintf("not enough results yet, want %d, got %d", num, actualNum) 161 } else if actualNum == num { 162 return succeeded, fmt.Sprintf("right number of results, want %d, got %d", num, actualNum) 163 } else { 164 return failed, fmt.Sprintf("too many results, want %d, got %d", num, actualNum) 165 } 166 } 167 } 168 169 func ResourceResultIs(result ResourceResult) Expectation { 170 return func(c *Consequences) (state, string) { 171 results := c.app().Status.OperationState.SyncResult.Resources 172 for _, res := range results { 173 if *res == result { 174 return succeeded, fmt.Sprintf("found resource result %v", result) 175 } 176 } 177 return pending, fmt.Sprintf("waiting for resource result %v in %v", result, results) 178 } 179 } 180 181 func sameResourceResult(res1, res2 ResourceResult) bool { 182 return res1.Kind == res2.Kind && 183 res1.Group == res2.Group && 184 res1.Namespace == res2.Namespace && 185 res1.Name == res2.Name && 186 res1.SyncPhase == res2.SyncPhase && 187 res1.Status == res2.Status && 188 res1.HookPhase == res2.HookPhase 189 } 190 191 func ResourceResultMatches(result ResourceResult) Expectation { 192 return func(c *Consequences) (state, string) { 193 results := c.app().Status.OperationState.SyncResult.Resources 194 for _, res := range results { 195 if sameResourceResult(*res, result) { 196 re := regexp.MustCompile(result.Message) 197 if re.MatchString(res.Message) { 198 return succeeded, fmt.Sprintf("found resource result %v", result) 199 } 200 } 201 } 202 return pending, fmt.Sprintf("waiting for resource result %v in %v", result, results) 203 } 204 } 205 206 func DoesNotExist() Expectation { 207 return func(c *Consequences) (state, string) { 208 _, err := c.get() 209 if err != nil { 210 if apierr.IsNotFound(err) { 211 return succeeded, "app does not exist" 212 } 213 return failed, err.Error() 214 } 215 return pending, "app should not exist" 216 } 217 } 218 219 func Pod(predicate func(p v1.Pod) bool) Expectation { 220 return func(c *Consequences) (state, string) { 221 pods, err := pods() 222 if err != nil { 223 return failed, err.Error() 224 } 225 for _, pod := range pods.Items { 226 if predicate(pod) { 227 return succeeded, fmt.Sprintf("pod predicate matched pod named '%s'", pod.GetName()) 228 } 229 } 230 return pending, "pod predicate does not match pods" 231 } 232 } 233 234 func NotPod(predicate func(p v1.Pod) bool) Expectation { 235 return func(c *Consequences) (state, string) { 236 pods, err := pods() 237 if err != nil { 238 return failed, err.Error() 239 } 240 for _, pod := range pods.Items { 241 if predicate(pod) { 242 return pending, fmt.Sprintf("pod predicate matched pod named '%s'", pod.GetName()) 243 } 244 } 245 return succeeded, "pod predicate did not match any pod" 246 } 247 } 248 249 func pods() (*v1.PodList, error) { 250 fixture.KubeClientset.CoreV1() 251 pods, err := fixture.KubeClientset.CoreV1().Pods(fixture.DeploymentNamespace()).List(context.Background(), metav1.ListOptions{}) 252 return pods, err 253 } 254 255 func NoNamespace(name string) Expectation { 256 return func(c *Consequences) (state, string) { 257 _, err := namespace(name) 258 259 if err != nil { 260 return succeeded, "namespace not found" 261 } 262 263 return failed, fmt.Sprintf("found namespace %s", name) 264 } 265 } 266 267 func namespace(name string) (*v1.Namespace, error) { 268 fixture.KubeClientset.CoreV1() 269 return fixture.KubeClientset.CoreV1().Namespaces().Get(context.Background(), name, metav1.GetOptions{}) 270 } 271 272 func event(namespace string, reason string, message string) Expectation { 273 return func(c *Consequences) (state, string) { 274 list, err := fixture.KubeClientset.CoreV1().Events(namespace).List(context.Background(), metav1.ListOptions{ 275 FieldSelector: fields.SelectorFromSet(map[string]string{ 276 "involvedObject.name": c.context.AppName(), 277 "involvedObject.namespace": namespace, 278 }).String(), 279 }) 280 if err != nil { 281 return failed, err.Error() 282 } 283 284 for i := range list.Items { 285 event := list.Items[i] 286 if event.Reason == reason && strings.Contains(event.Message, message) { 287 return succeeded, fmt.Sprintf("found event with reason=%s; message=%s", reason, message) 288 } 289 } 290 return failed, fmt.Sprintf("unable to find event with reason=%s; message=%s", reason, message) 291 } 292 } 293 294 func Event(reason string, message string) Expectation { 295 return event(fixture.TestNamespace(), reason, message) 296 } 297 298 func NamespacedEvent(namespace string, reason string, message string) Expectation { 299 return event(namespace, reason, message) 300 } 301 302 // Success asserts that the last command was successful and that the output contains the given message. 303 func Success(message string, matchers ...func(string, string) bool) Expectation { 304 if len(matchers) == 0 { 305 matchers = append(matchers, strings.Contains) 306 } 307 match := func(actual, expected string) bool { 308 for i := range matchers { 309 if !matchers[i](actual, expected) { 310 return false 311 } 312 } 313 return true 314 } 315 return func(c *Consequences) (state, string) { 316 if c.actions.lastError != nil { 317 return failed, "error" 318 } 319 if !match(c.actions.lastOutput, message) { 320 return failed, fmt.Sprintf("output did not contain '%s'", message) 321 } 322 return succeeded, fmt.Sprintf("no error and output contained '%s'", message) 323 } 324 } 325 326 // Error asserts that the last command was an error with substring match 327 func Error(message, err string, matchers ...func(string, string) bool) Expectation { 328 if len(matchers) == 0 { 329 matchers = append(matchers, strings.Contains) 330 } 331 match := func(actual, expected string) bool { 332 for i := range matchers { 333 if !matchers[i](actual, expected) { 334 return false 335 } 336 } 337 return true 338 } 339 return func(c *Consequences) (state, string) { 340 if c.actions.lastError == nil { 341 return failed, "no error" 342 } 343 if !match(c.actions.lastOutput, message) { 344 return failed, fmt.Sprintf("output does not contain '%s'", message) 345 } 346 if !match(c.actions.lastError.Error(), err) { 347 return failed, fmt.Sprintf("error does not contain '%s'", message) 348 } 349 return succeeded, fmt.Sprintf("error '%s'", message) 350 } 351 } 352 353 // ErrorRegex asserts that the last command was an error that matches given regex epxression 354 func ErrorRegex(messagePattern, err string) Expectation { 355 return Error(messagePattern, err, func(actual, expected string) bool { 356 return regexp.MustCompile(expected).MatchString(actual) 357 }) 358 } 359 360 // SuccessRegex asserts that the last command was successful and output matches given regex expression 361 func SuccessRegex(messagePattern string) Expectation { 362 return Success(messagePattern, func(actual, expected string) bool { 363 return regexp.MustCompile(expected).MatchString(actual) 364 }) 365 }