github.com/argoproj/argo-cd/v3@v3.2.1/test/e2e/fixture/applicationsets/expectation.go (about) 1 package applicationsets 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 "testing" 8 9 "github.com/argoproj/gitops-engine/pkg/diff" 10 corev1 "k8s.io/api/core/v1" 11 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 13 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 14 "github.com/argoproj/argo-cd/v3/test/e2e/fixture/applicationsets/utils" 15 ) 16 17 type state = string 18 19 const ( 20 failed = "failed" 21 pending = "pending" 22 succeeded = "succeeded" 23 ) 24 25 // Expectation returns succeeded on succes condition, or pending/failed on failure, along with 26 // a message to describe the success/failure condition. 27 type Expectation func(c *Consequences) (state state, message string) 28 29 // Success asserts that the last command was successful 30 func Success(message string) Expectation { 31 return func(c *Consequences) (state, string) { 32 if c.actions.lastError != nil { 33 return failed, fmt.Sprintf("error: %v", c.actions.lastError) 34 } 35 if !strings.Contains(c.actions.lastOutput, message) { 36 return failed, fmt.Sprintf("output did not contain '%s'", message) 37 } 38 return succeeded, fmt.Sprintf("no error and output contained '%s'", message) 39 } 40 } 41 42 // Error asserts that the last command was an error with substring match 43 func Error(message, err string) Expectation { 44 return func(c *Consequences) (state, string) { 45 if c.actions.lastError == nil { 46 return failed, "no error" 47 } 48 if !strings.Contains(c.actions.lastOutput, message) { 49 return failed, fmt.Sprintf("output does not contain '%s'", message) 50 } 51 if !strings.Contains(c.actions.lastError.Error(), err) { 52 return failed, fmt.Sprintf("error does not contain '%s'", message) 53 } 54 return succeeded, fmt.Sprintf("error '%s'", message) 55 } 56 } 57 58 // ApplicationsExist checks whether each of the 'expectedApps' exist in the namespace, and are 59 // equivalent to provided values. 60 func ApplicationsExist(expectedApps []v1alpha1.Application) Expectation { 61 return func(c *Consequences) (state, string) { 62 for _, expectedApp := range expectedApps { 63 foundApp := c.app(expectedApp.Name) 64 if foundApp == nil { 65 return pending, fmt.Sprintf("missing app '%s'", expectedApp.QualifiedName()) 66 } 67 68 if !appsAreEqual(expectedApp, *foundApp) { 69 diff, err := getDiff(filterFields(expectedApp), filterFields(*foundApp)) 70 if err != nil { 71 return failed, err.Error() 72 } 73 74 return pending, fmt.Sprintf("apps are not equal: '%s', diff: %s\n", expectedApp.QualifiedName(), diff) 75 } 76 } 77 78 return succeeded, "all apps successfully found" 79 } 80 } 81 82 // ApplicationSetHasConditions checks whether each of the 'expectedConditions' exist in the ApplicationSet status, and are 83 // equivalent to provided values. 84 func ApplicationSetHasConditions(applicationSetName string, expectedConditions []v1alpha1.ApplicationSetCondition) Expectation { 85 return func(c *Consequences) (state, string) { 86 // retrieve the application set 87 foundApplicationSet := c.applicationSet(applicationSetName) 88 if foundApplicationSet == nil { 89 return pending, fmt.Sprintf("application set '%s' not found", applicationSetName) 90 } 91 92 if !conditionsAreEqual(&expectedConditions, &foundApplicationSet.Status.Conditions) { 93 diff, err := getConditionDiff(expectedConditions, foundApplicationSet.Status.Conditions) 94 if err != nil { 95 return failed, err.Error() 96 } 97 return pending, fmt.Sprintf("application set conditions are not equal: '%s', diff: %s\n", expectedConditions, diff) 98 } 99 return succeeded, "application set successfully found" 100 } 101 } 102 103 // ApplicationsDoNotExist checks that each of the 'expectedApps' no longer exist in the namespace 104 func ApplicationsDoNotExist(expectedApps []v1alpha1.Application) Expectation { 105 return func(c *Consequences) (state, string) { 106 for _, expectedApp := range expectedApps { 107 foundApp := c.app(expectedApp.Name) 108 if foundApp != nil { 109 return pending, fmt.Sprintf("app '%s' should no longer exist", expectedApp.QualifiedName()) 110 } 111 } 112 113 return succeeded, "all apps do not exist" 114 } 115 } 116 117 // Pod checks whether a specified condition is true for any of the pods in the namespace 118 func Pod(t *testing.T, predicate func(p corev1.Pod) bool) Expectation { 119 t.Helper() 120 return func(_ *Consequences) (state, string) { 121 pods, err := pods(t, utils.ApplicationsResourcesNamespace) 122 if err != nil { 123 return failed, err.Error() 124 } 125 for _, pod := range pods.Items { 126 if predicate(pod) { 127 return succeeded, fmt.Sprintf("pod predicate matched pod named '%s'", pod.GetName()) 128 } 129 } 130 return pending, "pod predicate does not match pods" 131 } 132 } 133 134 func pods(t *testing.T, namespace string) (*corev1.PodList, error) { 135 t.Helper() 136 fixtureClient := utils.GetE2EFixtureK8sClient(t) 137 138 pods, err := fixtureClient.KubeClientset.CoreV1().Pods(namespace).List(t.Context(), metav1.ListOptions{}) 139 return pods, err 140 } 141 142 // getDiff returns a string containing a comparison result of two applications (for test output/debug purposes) 143 func getDiff(orig, newApplication v1alpha1.Application) (string, error) { 144 bytes, _, err := diff.CreateTwoWayMergePatch(orig, newApplication, orig) 145 if err != nil { 146 return "", err 147 } 148 149 return string(bytes), nil 150 } 151 152 // getConditionDiff returns a string containing a comparison result of two ApplicationSetCondition (for test output/debug purposes) 153 func getConditionDiff(orig, newApplicationSetCondition []v1alpha1.ApplicationSetCondition) (string, error) { 154 if len(orig) != len(newApplicationSetCondition) { 155 return fmt.Sprintf("mismatch between condition sizes: %v %v", len(orig), len(newApplicationSetCondition)), nil 156 } 157 158 var bytes []byte 159 160 for index := range orig { 161 b, _, err := diff.CreateTwoWayMergePatch(orig[index], newApplicationSetCondition[index], orig[index]) 162 if err != nil { 163 return "", err 164 } 165 bytes = append(bytes, b...) 166 } 167 168 return string(bytes), nil 169 } 170 171 // filterFields returns a copy of Application, but with unnecessary (for testing) fields removed 172 func filterFields(input v1alpha1.Application) v1alpha1.Application { 173 spec := input.Spec 174 175 metaCopy := input.ObjectMeta.DeepCopy() 176 177 output := v1alpha1.Application{ 178 ObjectMeta: metav1.ObjectMeta{ 179 Labels: metaCopy.Labels, 180 Annotations: metaCopy.Annotations, 181 Name: metaCopy.Name, 182 Namespace: metaCopy.Namespace, 183 Finalizers: metaCopy.Finalizers, 184 }, 185 Spec: v1alpha1.ApplicationSpec{ 186 Source: &v1alpha1.ApplicationSource{ 187 Path: spec.GetSource().Path, 188 RepoURL: spec.GetSource().RepoURL, 189 TargetRevision: spec.GetSource().TargetRevision, 190 }, 191 Destination: v1alpha1.ApplicationDestination{ 192 Server: spec.Destination.Server, 193 Name: spec.Destination.Name, 194 Namespace: spec.Destination.Namespace, 195 }, 196 Project: spec.Project, 197 }, 198 } 199 200 return output 201 } 202 203 // filterConditionFields returns a copy of ApplicationSetCondition, but with unnecessary (for testing) fields removed 204 func filterConditionFields(input *[]v1alpha1.ApplicationSetCondition) *[]v1alpha1.ApplicationSetCondition { 205 var filteredConditions []v1alpha1.ApplicationSetCondition 206 for _, condition := range *input { 207 newCondition := &v1alpha1.ApplicationSetCondition{ 208 Type: condition.Type, 209 Status: condition.Status, 210 Message: condition.Message, 211 Reason: condition.Reason, 212 } 213 filteredConditions = append(filteredConditions, *newCondition) 214 } 215 216 return &filteredConditions 217 } 218 219 // appsAreEqual returns true if the apps are equal, comparing only fields of interest 220 func appsAreEqual(one v1alpha1.Application, two v1alpha1.Application) bool { 221 return reflect.DeepEqual(filterFields(one), filterFields(two)) 222 } 223 224 // conditionsAreEqual returns true if the appset status conditions are equal, comparing only fields of interest 225 func conditionsAreEqual(one, two *[]v1alpha1.ApplicationSetCondition) bool { 226 return reflect.DeepEqual(filterConditionFields(one), filterConditionFields(two)) 227 }