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 }