github.com/argoproj/argo-cd/v3@v3.2.1/test/e2e/fixture/app/consequences.go (about) 1 package app 2 3 import ( 4 "context" 5 "time" 6 7 "github.com/argoproj/gitops-engine/pkg/health" 8 log "github.com/sirupsen/logrus" 9 "github.com/stretchr/testify/require" 10 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 "k8s.io/utils/ptr" 12 13 applicationpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/application" 14 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 15 "github.com/argoproj/argo-cd/v3/test/e2e/fixture" 16 utilio "github.com/argoproj/argo-cd/v3/util/io" 17 ) 18 19 // this implements the "then" part of given/when/then 20 type Consequences struct { 21 context *Context 22 actions *Actions 23 timeout int 24 } 25 26 func (c *Consequences) Expect(e Expectation) *Consequences { 27 // this invocation makes sure this func is not reported as the cause of the failure - we are a "test helper" 28 c.context.t.Helper() 29 var message string 30 var state state 31 sleepIntervals := []time.Duration{ 32 10 * time.Millisecond, 33 20 * time.Millisecond, 34 50 * time.Millisecond, 35 100 * time.Millisecond, 36 200 * time.Millisecond, 37 300 * time.Millisecond, 38 500 * time.Millisecond, 39 1 * time.Second, 40 } 41 sleepIntervalsIdx := -1 42 timeout := time.Duration(c.timeout) * time.Second 43 for start := time.Now(); time.Since(start) < timeout; time.Sleep(sleepIntervals[sleepIntervalsIdx]) { 44 if sleepIntervalsIdx < len(sleepIntervals)-1 { 45 sleepIntervalsIdx++ 46 } 47 state, message = e(c) 48 switch state { 49 case succeeded: 50 log.Infof("expectation succeeded: %s", message) 51 return c 52 case failed: 53 c.context.t.Fatalf("failed expectation: %s", message) 54 return c 55 } 56 log.Infof("pending: %s", message) 57 } 58 c.context.t.Fatal("timeout waiting for: " + message) 59 return c 60 } 61 62 // ExpectConsistently will continuously evaluate a condition, and it must be true each time it is evaluated, otherwise the test is failed. The condition will be repeatedly evaluated until 'expirationDuration' is met, waiting 'waitDuration' after each success. 63 func (c *Consequences) ExpectConsistently(e Expectation, waitDuration time.Duration, expirationDuration time.Duration) *Consequences { 64 // this invocation makes sure this func is not reported as the cause of the failure - we are a "test helper" 65 c.context.t.Helper() 66 67 expiration := time.Now().Add(expirationDuration) 68 for time.Now().Before(expiration) { 69 state, message := e(c) 70 switch state { 71 case succeeded: 72 log.Infof("expectation succeeded: %s", message) 73 case failed: 74 c.context.t.Fatalf("failed expectation: %s", message) 75 return c 76 } 77 78 // On condition success: wait, then retry 79 log.Infof("Expectation '%s' passes, repeating to ensure consistency", message) 80 time.Sleep(waitDuration) 81 } 82 83 // If the condition never failed before expiring, it is a pass. 84 return c 85 } 86 87 func (c *Consequences) And(block func(app *v1alpha1.Application)) *Consequences { 88 c.context.t.Helper() 89 block(c.app()) 90 return c 91 } 92 93 func (c *Consequences) AndAction(block func()) *Consequences { 94 c.context.t.Helper() 95 block() 96 return c 97 } 98 99 func (c *Consequences) Given() *Context { 100 return c.context 101 } 102 103 func (c *Consequences) When() *Actions { 104 time.Sleep(fixture.WhenThenSleepInterval) 105 return c.actions 106 } 107 108 func (c *Consequences) app() *v1alpha1.Application { 109 c.context.t.Helper() 110 app, err := c.get() 111 require.NoError(c.context.t, err) 112 return app 113 } 114 115 func (c *Consequences) get() (*v1alpha1.Application, error) { 116 return fixture.AppClientset.ArgoprojV1alpha1().Applications(c.context.AppNamespace()).Get(context.Background(), c.context.AppName(), metav1.GetOptions{}) 117 } 118 119 func (c *Consequences) resource(kind, name, namespace string) v1alpha1.ResourceStatus { 120 c.context.t.Helper() 121 closer, client, err := fixture.ArgoCDClientset.NewApplicationClient() 122 require.NoError(c.context.t, err) 123 defer utilio.Close(closer) 124 app, err := client.Get(context.Background(), &applicationpkg.ApplicationQuery{ 125 Name: ptr.To(c.context.AppName()), 126 Projects: []string{c.context.project}, 127 AppNamespace: ptr.To(c.context.appNamespace), 128 }) 129 require.NoError(c.context.t, err) 130 for _, r := range app.Status.Resources { 131 if r.Kind == kind && r.Name == name && (namespace == "" || namespace == r.Namespace) { 132 return r 133 } 134 } 135 return v1alpha1.ResourceStatus{ 136 Health: &v1alpha1.HealthStatus{ 137 Status: health.HealthStatusMissing, 138 Message: "not found", 139 }, 140 } 141 } 142 143 func (c *Consequences) AndCLIOutput(block func(output string, err error)) *Consequences { 144 c.context.t.Helper() 145 block(c.actions.lastOutput, c.actions.lastError) 146 return c 147 }