github.com/alibabacloud-go/tea@v1.3.10/dara/retry_test.go (about)

     1  package dara
     2  
     3  import (
     4  	// "fmt"
     5  	// "math"
     6  	"math/rand"
     7  	"testing"
     8  )
     9  
    10  type AErr struct {
    11  	BaseError
    12  	Code    *string
    13  	Name    *string
    14  	Message *string
    15  }
    16  
    17  func (err *AErr) New(obj map[string]interface{}) *AErr {
    18  
    19  	err.Name = String("AErr")
    20  
    21  	if val, ok := obj["code"].(string); ok {
    22  		err.Code = String(val)
    23  	}
    24  
    25  	if val, ok := obj["message"].(string); ok {
    26  		err.Message = String(val)
    27  	}
    28  
    29  	return err
    30  }
    31  
    32  func (err *AErr) GetCode() *string {
    33  	return err.Code
    34  }
    35  
    36  func (err *AErr) GetName() *string {
    37  	return err.Name
    38  }
    39  
    40  type BErr struct {
    41  	BaseError
    42  	Code    *string
    43  	Name    *string
    44  	Message *string
    45  }
    46  
    47  func (err *BErr) New(obj map[string]interface{}) *BErr {
    48  
    49  	err.Name = String("BErr")
    50  
    51  	if val, ok := obj["code"].(string); ok {
    52  		err.Code = String(val)
    53  	}
    54  
    55  	if val, ok := obj["message"].(string); ok {
    56  		err.Message = String(val)
    57  	}
    58  
    59  	return err
    60  }
    61  
    62  func (err *BErr) GetCode() *string {
    63  	return err.Code
    64  }
    65  
    66  func (err *BErr) GetName() *string {
    67  	return err.Name
    68  }
    69  
    70  type CErr struct {
    71  	ResponseError
    72  	Code       *string
    73  	Name       *string
    74  	Message    *string
    75  	RetryAfter *int64
    76  	StatusCode *int
    77  }
    78  
    79  func (err *CErr) New(obj map[string]interface{}) *CErr {
    80  	err.Name = String("CErr")
    81  
    82  	if val, ok := obj["code"].(string); ok {
    83  		err.Code = String(val)
    84  	}
    85  
    86  	if val, ok := obj["message"].(string); ok {
    87  		err.Message = String(val)
    88  	}
    89  
    90  	if statusCode, ok := obj["StatusCode"].(int); ok {
    91  		err.StatusCode = Int(statusCode)
    92  	}
    93  
    94  	if retryAfter, ok := obj["RetryAfter"].(int64); ok {
    95  		err.RetryAfter = Int64(retryAfter)
    96  	}
    97  
    98  	return err
    99  }
   100  
   101  func (err *CErr) GetCode() *string {
   102  	return err.Code
   103  }
   104  
   105  func (err *CErr) GetName() *string {
   106  	return err.Name
   107  }
   108  
   109  func (err *CErr) GetRetryAfter() *int64 {
   110  	return err.RetryAfter
   111  }
   112  
   113  func (err *CErr) GetStatusCode() *int {
   114  	return err.StatusCode
   115  }
   116  
   117  // BackoffPolicyFactory creates a BackoffPolicy based on the option
   118  func TestBackoffPolicyFactory(t *testing.T) {
   119  	tests := []struct {
   120  		name          string
   121  		option        map[string]interface{}
   122  		expectedError bool
   123  	}{
   124  		{
   125  			name: "Fixed policy",
   126  			option: map[string]interface{}{
   127  				"policy": "Fixed",
   128  			},
   129  			expectedError: false,
   130  		},
   131  		{
   132  			name: "Random policy",
   133  			option: map[string]interface{}{
   134  				"policy": "Random",
   135  			},
   136  			expectedError: false,
   137  		},
   138  		{
   139  			name: "Exponential policy",
   140  			option: map[string]interface{}{
   141  				"policy": "Exponential",
   142  			},
   143  			expectedError: false,
   144  		},
   145  		{
   146  			name: "EqualJitter policy",
   147  			option: map[string]interface{}{
   148  				"policy": "EqualJitter",
   149  			},
   150  			expectedError: false,
   151  		},
   152  		{
   153  			name: "FullJitter policy",
   154  			option: map[string]interface{}{
   155  				"policy": "FullJitter",
   156  			},
   157  			expectedError: false,
   158  		},
   159  		{
   160  			name: "Unknown policy",
   161  			option: map[string]interface{}{
   162  				"policy": "Unknown",
   163  			},
   164  			expectedError: true,
   165  		},
   166  	}
   167  
   168  	for _, tt := range tests {
   169  		t.Run(tt.name, func(t *testing.T) {
   170  			backoffPolicy, err := BackoffPolicyFactory(tt.option)
   171  			if (err != nil) != tt.expectedError {
   172  				t.Errorf("expected error: %v, got: %v", tt.expectedError, err)
   173  			}
   174  
   175  			if !tt.expectedError && backoffPolicy == nil {
   176  				t.Errorf("expected a valid BackoffPolicy, got nil")
   177  			}
   178  		})
   179  	}
   180  }
   181  
   182  func TestShouldRetry(t *testing.T) {
   183  	tests := []struct {
   184  		name     string
   185  		options  RetryOptions
   186  		ctx      RetryPolicyContext
   187  		expected bool
   188  	}{
   189  		{
   190  			name:     "Should not retry when options are nil",
   191  			options:  RetryOptions{},
   192  			ctx:      RetryPolicyContext{},
   193  			expected: true,
   194  		},
   195  		{
   196  			name: "Should not retry when retries exhausted",
   197  			options: RetryOptions{
   198  				Retryable: true,
   199  				RetryCondition: []*RetryCondition{
   200  					{MaxAttempts: 3, Exception: []string{"AErr"}, ErrorCode: []string{"A1Err"}},
   201  				},
   202  			},
   203  			ctx: RetryPolicyContext{
   204  				RetriesAttempted: 3,
   205  				Exception:        new(AErr).New(map[string]interface{}{"Code": "A1Err"}),
   206  			},
   207  			expected: false,
   208  		},
   209  		{
   210  			name: "Should retry when conditions match",
   211  			options: RetryOptions{
   212  				Retryable: true,
   213  				RetryCondition: []*RetryCondition{
   214  					{MaxAttempts: 3, Exception: []string{"AErr"}, ErrorCode: []string{"A1Err"}},
   215  				},
   216  			},
   217  			ctx: RetryPolicyContext{
   218  				RetriesAttempted: 2,
   219  				Exception:        new(AErr).New(map[string]interface{}{"Code": "A1Err"}),
   220  			},
   221  			expected: true,
   222  		},
   223  		{
   224  			name: "Should retry for different exception",
   225  			options: RetryOptions{
   226  				Retryable: true,
   227  				RetryCondition: []*RetryCondition{
   228  					{MaxAttempts: 3, Exception: []string{"AErr"}, ErrorCode: []string{"A1Err"}},
   229  				},
   230  			},
   231  			ctx: RetryPolicyContext{
   232  				RetriesAttempted: 2,
   233  				Exception:        new(BErr).New(map[string]interface{}{"Code": "B1Err"}),
   234  			},
   235  			expected: false,
   236  		},
   237  		{
   238  			name: "Should not retry with no retry condition",
   239  			options: RetryOptions{
   240  				Retryable: true,
   241  				RetryCondition: []*RetryCondition{
   242  					{MaxAttempts: 3, Exception: []string{"BErr"}, ErrorCode: []string{"B1Err"}},
   243  				},
   244  				NoRetryCondition: []*RetryCondition{
   245  					{MaxAttempts: 3, Exception: []string{"AErr"}, ErrorCode: []string{"B1Err"}},
   246  				},
   247  			},
   248  			ctx: RetryPolicyContext{
   249  				RetriesAttempted: 2,
   250  				Exception:        new(AErr).New(map[string]interface{}{"Code": "B1Err"}),
   251  			},
   252  			expected: false,
   253  		},
   254  	}
   255  
   256  	for _, test := range tests {
   257  		t.Run(test.name, func(t *testing.T) {
   258  			got := ShouldRetry(&test.options, &test.ctx)
   259  			if got != test.expected {
   260  				t.Errorf("expected %v, got %v", test.expected, got)
   261  			}
   262  		})
   263  	}
   264  }
   265  
   266  func TestFixedBackoffPolicy(t *testing.T) {
   267  
   268  	condition1 := NewRetryCondition(map[string]interface{}{
   269  		"maxAttempts": 3,
   270  		"exception":   []string{"AErr"},
   271  		"errorCode":   []string{"A1Err"},
   272  		"backoff": map[string]interface{}{
   273  			"policy": "Fixed",
   274  			"period": 1000,
   275  		},
   276  	})
   277  
   278  	options := RetryOptions{
   279  		Retryable:      true,
   280  		RetryCondition: []*RetryCondition{condition1},
   281  	}
   282  
   283  	context := RetryPolicyContext{
   284  		RetriesAttempted: 2,
   285  		Exception: new(AErr).New(map[string]interface{}{
   286  			"Code": "A1Err",
   287  		}),
   288  	}
   289  
   290  	// Test Delay Time
   291  	expectedDelay := 1000
   292  	if delay := GetBackoffDelay(&options, &context); delay != expectedDelay {
   293  		t.Errorf("Expected delay time %d, got %d", expectedDelay, delay)
   294  	}
   295  }
   296  
   297  func TestRandomBackoffPolicy(t *testing.T) {
   298  	// Test case 1: Random backoff policy with period of 1000 and cap of 10000
   299  	rand.Seed(42) // Set seed for reproducibility
   300  	condition1 := NewRetryCondition(map[string]interface{}{
   301  		"maxAttempts": 3,
   302  		"exception":   []string{"AErr"},
   303  		"errorCode":   []string{"A1Err"},
   304  		"backoff": map[string]interface{}{
   305  			"policy": "Random",
   306  			"period": 1000,
   307  			"cap":    10000,
   308  		},
   309  	})
   310  
   311  	options := RetryOptions{
   312  		Retryable:      true,
   313  		RetryCondition: []*RetryCondition{condition1},
   314  	}
   315  
   316  	context := RetryPolicyContext{
   317  		RetriesAttempted: 2,
   318  		Exception: new(AErr).New(map[string]interface{}{
   319  			"Code": "A1Err",
   320  		}),
   321  	}
   322  
   323  	// Test Delay Time
   324  	delay := GetBackoffDelay(&options, &context)
   325  	if delay >= 10000 {
   326  		t.Errorf("Expected backoff delay to be less than 10000, got %d", delay)
   327  	}
   328  
   329  	// Test case 2: Random backoff policy with period of 10000 and cap of 10
   330  	condition2 := NewRetryCondition(map[string]interface{}{
   331  		"maxAttempts": 3,
   332  		"exception":   []string{"AErr"},
   333  		"errorCode":   []string{"A1Err"},
   334  		"backoff": map[string]interface{}{
   335  			"policy": "Random",
   336  			"period": 1000,
   337  			"cap":    10,
   338  		},
   339  	})
   340  
   341  	options = RetryOptions{
   342  		Retryable:      true,
   343  		RetryCondition: []*RetryCondition{condition2},
   344  	}
   345  
   346  	delay2 := GetBackoffDelay(&options, &context)
   347  	if delay2 != 10 {
   348  		t.Errorf("Expected backoff delay to be 10, got %d", delay2)
   349  	}
   350  }
   351  
   352  func TestExponentialBackoffPolicy(t *testing.T) {
   353  	// Test case 1
   354  	condition1 := NewRetryCondition(map[string]interface{}{
   355  		"maxAttempts": 3,
   356  		"exception":   []string{"AErr"},
   357  		"errorCode":   []string{"A1Err"},
   358  		"backoff": map[string]interface{}{
   359  			"policy": "Exponential",
   360  			"period": 5,
   361  			"cap":    10000,
   362  		},
   363  	})
   364  
   365  	options := RetryOptions{
   366  		Retryable:      true,
   367  		RetryCondition: []*RetryCondition{condition1},
   368  	}
   369  
   370  	context := RetryPolicyContext{
   371  		RetriesAttempted: 2,
   372  		Exception: new(AErr).New(map[string]interface{}{
   373  			"Code": "A1Err",
   374  		}),
   375  	}
   376  
   377  	// Test Delay Time
   378  	delay := GetBackoffDelay(&options, &context)
   379  	if delay != 1024 {
   380  		t.Errorf("Expected backoff delay to be 1024, got %d", delay)
   381  	}
   382  
   383  	// Test case 2
   384  	condition2 := NewRetryCondition(map[string]interface{}{
   385  		"maxAttempts": 3,
   386  		"exception":   []string{"AErr"},
   387  		"errorCode":   []string{"A1Err"},
   388  		"backoff": map[string]interface{}{
   389  			"policy": "Exponential",
   390  			"period": 10,
   391  			"cap":    10000,
   392  		},
   393  	})
   394  
   395  	options = RetryOptions{
   396  		Retryable:      true,
   397  		RetryCondition: []*RetryCondition{condition2},
   398  	}
   399  
   400  	// Test Delay Time
   401  	delay = GetBackoffDelay(&options, &context)
   402  	if delay != 10000 {
   403  		t.Errorf("Expected backoff delay to be 10000, got %d", delay)
   404  	}
   405  }
   406  
   407  func TestEqualJitterBackoff(t *testing.T) {
   408  	rand.Seed(0) // Seed random for predictable results
   409  	// Test case 1
   410  	condition1 := NewRetryCondition(map[string]interface{}{
   411  		"maxAttempts": 3,
   412  		"exception":   []string{"AErr"},
   413  		"errorCode":   []string{"A1Err"},
   414  		"backoff": map[string]interface{}{
   415  			"policy": "EqualJitter",
   416  			"period": 5,
   417  			"cap":    10000,
   418  		},
   419  	})
   420  
   421  	options := RetryOptions{
   422  		Retryable:      true,
   423  		RetryCondition: []*RetryCondition{condition1},
   424  	}
   425  
   426  	context := RetryPolicyContext{
   427  		RetriesAttempted: 2,
   428  		Exception: new(AErr).New(map[string]interface{}{
   429  			"Code": "A1Err",
   430  		}),
   431  	}
   432  
   433  	// Test Delay Time
   434  	delay := GetBackoffDelay(&options, &context)
   435  	if delay <= 512 || delay >= 1024 {
   436  		t.Errorf("Expected backoff time in range (512, 1024), got: %d", delay)
   437  	}
   438  
   439  	// Test case 2
   440  	condition2 := NewRetryCondition(map[string]interface{}{
   441  		"maxAttempts": 3,
   442  		"exception":   []string{"AErr"},
   443  		"errorCode":   []string{"A1Err"},
   444  		"backoff": map[string]interface{}{
   445  			"policy": "ExponentialWithEqualJitter",
   446  			"period": 10,
   447  			"cap":    10000,
   448  		},
   449  	})
   450  
   451  	options = RetryOptions{
   452  		Retryable:      true,
   453  		RetryCondition: []*RetryCondition{condition2},
   454  	}
   455  
   456  	// Test Delay Time
   457  	delay = GetBackoffDelay(&options, &context)
   458  	if delay <= 5000 || delay >= 10000 {
   459  		t.Errorf("Expected backoff time in range (5000, 10000), got: %d", delay)
   460  	}
   461  }
   462  
   463  func TestFullJitterBackoffPolicy(t *testing.T) {
   464  	// Test case 1
   465  	condition1 := NewRetryCondition(map[string]interface{}{
   466  		"maxAttempts": 3,
   467  		"exception":   []string{"AErr"},
   468  		"errorCode":   []string{"A1Err"},
   469  		"backoff": map[string]interface{}{
   470  			"policy": "fullJitter",
   471  			"period": 5,
   472  			"cap":    10000,
   473  		},
   474  	})
   475  
   476  	options := RetryOptions{
   477  		Retryable:      true,
   478  		RetryCondition: []*RetryCondition{condition1},
   479  	}
   480  
   481  	context := RetryPolicyContext{
   482  		RetriesAttempted: 2,
   483  		Exception: new(AErr).New(map[string]interface{}{
   484  			"Code": "A1Err",
   485  		}),
   486  	}
   487  
   488  	// Test Delay Time
   489  	delay := GetBackoffDelay(&options, &context)
   490  	if delay < 0 || delay >= 1024 {
   491  		t.Errorf("Expected backoff time in range [0, 1024), got: %d", delay)
   492  	}
   493  
   494  	condition2 := NewRetryCondition(map[string]interface{}{
   495  		"maxAttempts": 3,
   496  		"exception":   []string{"AErr"},
   497  		"errorCode":   []string{"A1Err"},
   498  		"backoff": map[string]interface{}{
   499  			"policy": "ExponentialWithFullJitter",
   500  			"period": 10,
   501  			"cap":    10000,
   502  		},
   503  	})
   504  
   505  	options = RetryOptions{
   506  		Retryable:      true,
   507  		RetryCondition: []*RetryCondition{condition2},
   508  	}
   509  
   510  	// Test Delay Time
   511  	delay = GetBackoffDelay(&options, &context)
   512  	if delay < 0 || delay >= 10000 {
   513  		t.Errorf("Expected backoff time in range [0, 10000), got: %d", delay)
   514  	}
   515  
   516  	// Test case 3 with maxDelay
   517  	condition3 := NewRetryCondition(map[string]interface{}{
   518  		"maxAttempts": 3,
   519  		"exception":   []string{"AErr"},
   520  		"errorCode":   []string{"A1Err"},
   521  		"MaxDelay":    1000,
   522  		"backoff": map[string]interface{}{
   523  			"policy": "ExponentialWithFullJitter",
   524  			"period": 10,
   525  			"cap":    10000,
   526  		},
   527  	})
   528  
   529  	options = RetryOptions{
   530  		Retryable:      true,
   531  		RetryCondition: []*RetryCondition{condition3},
   532  	}
   533  
   534  	// Test Delay Time
   535  	delay = GetBackoffDelay(&options, &context)
   536  	if delay < 0 || delay > 10000 {
   537  		t.Errorf("Expected backoff time in range [0, 10000], got: %d", delay)
   538  	}
   539  
   540  	// Test case 4
   541  	condition4 := NewRetryCondition(map[string]interface{}{
   542  		"maxAttempts": 3,
   543  		"exception":   []string{"AErr"},
   544  		"errorCode":   []string{"A1Err"},
   545  		"backoff": map[string]interface{}{
   546  			"policy": "ExponentialWithFullJitter",
   547  			"period": 10,
   548  			"cap":    10000 * 10000,
   549  		},
   550  	})
   551  
   552  	options = RetryOptions{
   553  		Retryable:      true,
   554  		RetryCondition: []*RetryCondition{condition4},
   555  	}
   556  
   557  	// Test Delay Time
   558  	delay = GetBackoffDelay(&options, &context)
   559  	if delay < 0 || delay > 120*1000 {
   560  		t.Errorf("Expected backoff time in range [0, 120000], got: %d", delay)
   561  	}
   562  
   563  }
   564  
   565  func TestRetryAfter(t *testing.T) {
   566  	condition1 := NewRetryCondition(map[string]interface{}{
   567  		"maxAttempts": 3,
   568  		"exception":   []string{"CErr"},
   569  		"errorCode":   []string{"CErr"},
   570  		"MaxDelay":    5000,
   571  		"backoff": map[string]interface{}{
   572  			"policy": "EqualJitter",
   573  			"period": 10,
   574  			"cap":    10000,
   575  		},
   576  	})
   577  
   578  	options := RetryOptions{
   579  		Retryable:      true,
   580  		RetryCondition: []*RetryCondition{condition1},
   581  	}
   582  
   583  	context := RetryPolicyContext{
   584  		RetriesAttempted: 2,
   585  		Exception: new(CErr).New(map[string]interface{}{
   586  			"Code":       "CErr",
   587  			"RetryAfter": int64(3000),
   588  		}),
   589  	}
   590  
   591  	// Test Delay Time
   592  	delay := GetBackoffDelay(&options, &context)
   593  	if delay != 3000 {
   594  		t.Errorf("Expected backoff time must be 3000, got: %d", delay)
   595  	}
   596  
   597  	condition2 := NewRetryCondition(map[string]interface{}{
   598  		"maxAttempts": 3,
   599  		"exception":   []string{"CErr"},
   600  		"errorCode":   []string{"CErr"},
   601  		"maxDelay":    1000,
   602  		"backoff": map[string]interface{}{
   603  			"policy": "EqualJitter",
   604  			"period": 10,
   605  			"cap":    10000,
   606  		},
   607  	})
   608  
   609  	options = RetryOptions{
   610  		Retryable:      true,
   611  		RetryCondition: []*RetryCondition{condition2},
   612  	}
   613  
   614  	// Test Delay Time
   615  	delay = GetBackoffDelay(&options, &context)
   616  	if delay != 1000 {
   617  		t.Errorf("Expected backoff time must be 1000, got: %d", delay)
   618  	}
   619  }