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  }