go.mercari.io/datastore@v1.8.2/dsmiddleware/rpcretry/rpcretry_test.go (about) 1 package rpcretry 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "regexp" 8 "strings" 9 "testing" 10 "time" 11 12 "github.com/MakeNowJust/heredoc/v2" 13 "go.mercari.io/datastore" 14 "go.mercari.io/datastore/dsmiddleware/dslog" 15 "go.mercari.io/datastore/internal/testutils" 16 "google.golang.org/api/iterator" 17 ) 18 19 func TestRPCRetry_waitDuration(t *testing.T) { 20 specs := []struct { 21 rh *retryHandler 22 retries []int 23 expectedList []time.Duration 24 }{ 25 { // minBackoffDuration 26 &retryHandler{ 27 minBackoffDuration: time.Millisecond, 28 }, 29 []int{ 30 1, 2, 3, 31 }, 32 []time.Duration{ 33 1 * time.Millisecond, 2 * time.Millisecond, 4 * time.Millisecond, 34 }, 35 }, 36 { // maxBackoffDuration 37 &retryHandler{ 38 minBackoffDuration: time.Millisecond, 39 maxBackoffDuration: 3 * time.Millisecond, 40 }, 41 []int{ 42 1, 2, 3, 43 }, 44 []time.Duration{ 45 1 * time.Millisecond, 2 * time.Millisecond, 3 * time.Millisecond, 46 }, 47 }, 48 { // maxDoublings 49 &retryHandler{ 50 minBackoffDuration: time.Millisecond, 51 maxDoublings: 2, 52 }, 53 []int{ 54 1, 2, 3, 55 }, 56 []time.Duration{ 57 1 * time.Millisecond, 2 * time.Millisecond, 2 * time.Millisecond, 58 }, 59 }, 60 { // all zero 61 &retryHandler{}, 62 []int{ 63 1, 2, 3, 64 }, 65 []time.Duration{ 66 0, 0, 0, 67 }, 68 }, 69 } 70 71 for specIdx, spec := range specs { 72 for idx, retry := range spec.retries { 73 expected := spec.expectedList[idx] 74 75 ret := spec.rh.waitDuration(retry) 76 if ret != expected { 77 t.Errorf("#%d-%d expected: %d, actual: %d", specIdx+1, idx+1, expected, ret) 78 } 79 } 80 } 81 } 82 83 func TestRPCRetry_Basic(t *testing.T) { 84 ctx, client, cleanUp := testutils.SetupCloudDatastore(t) 85 defer cleanUp() 86 87 var logs []string 88 logf := func(ctx context.Context, format string, args ...interface{}) { 89 t.Logf(format, args...) 90 logs = append(logs, fmt.Sprintf(format, args...)) 91 } 92 93 // setup. strategies are first in - first apply. 94 95 bLog := dslog.NewLogger("before: ", logf) 96 client.AppendMiddleware(bLog) 97 defer func() { 98 // stop logging before cleanUp func called. 99 client.RemoveMiddleware(bLog) 100 }() 101 102 rh := New( 103 WithLogger(logf), 104 WithMinBackoffDuration(1), 105 WithRetryLimit(4), 106 ) 107 client.AppendMiddleware(rh) 108 defer func() { 109 // stop logging before cleanUp func called. 110 client.RemoveMiddleware(rh) 111 }() 112 113 aLog := dslog.NewLogger("after: ", logf) 114 client.AppendMiddleware(aLog) 115 defer func() { 116 // stop logging before cleanUp func called. 117 client.RemoveMiddleware(aLog) 118 }() 119 120 gm := &glitchEmulator{errCount: 3} 121 client.AppendMiddleware(gm) 122 defer func() { 123 // stop logging before cleanUp func called. 124 client.RemoveMiddleware(gm) 125 }() 126 127 type Data struct { 128 Name string 129 } 130 131 // Put. 132 key := client.IDKey("Data", 111, nil) 133 objBefore := &Data{Name: "Data"} 134 _, err := client.Put(ctx, key, objBefore) 135 if err != nil { 136 t.Fatal(err) 137 } 138 139 // Get. 140 objAfter := &Data{} 141 err = client.Get(ctx, key, objAfter) 142 if err != nil { 143 t.Fatal(err) 144 } 145 146 // Delete. 147 err = client.Delete(ctx, key) 148 if err != nil { 149 t.Fatal(err) 150 } 151 152 expected := heredoc.Doc(` 153 ## Put. 154 before: PutMultiWithoutTx #1, len(keys)=1, keys=[/Data,111] 155 after: PutMultiWithoutTx #1, len(keys)=1, keys=[/Data,111] 156 after: PutMultiWithoutTx #1, err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,111 157 middleware/rpcretry.PutMultiWithoutTx: err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,111, will be retry #1 after 1ns 158 after: PutMultiWithoutTx #2, len(keys)=1, keys=[/Data,111] 159 after: PutMultiWithoutTx #2, err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,111 160 middleware/rpcretry.PutMultiWithoutTx: err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,111, will be retry #2 after 2ns 161 after: PutMultiWithoutTx #3, len(keys)=1, keys=[/Data,111] 162 after: PutMultiWithoutTx #3, err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,111 163 middleware/rpcretry.PutMultiWithoutTx: err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,111, will be retry #3 after 4ns 164 after: PutMultiWithoutTx #4, len(keys)=1, keys=[/Data,111] 165 after: PutMultiWithoutTx #4, keys=[/Data,111] 166 before: PutMultiWithoutTx #1, keys=[/Data,111] 167 ## Get. 168 before: GetMultiWithoutTx #2, len(keys)=1, keys=[/Data,111] 169 after: GetMultiWithoutTx #5, len(keys)=1, keys=[/Data,111] 170 after: GetMultiWithoutTx #5, err=error by *glitchEmulator: GetMultiWithoutTx, keys=/Data,111 171 middleware/rpcretry.GetMultiWithoutTx: err=error by *glitchEmulator: GetMultiWithoutTx, keys=/Data,111, will be retry #1 after 1ns 172 after: GetMultiWithoutTx #6, len(keys)=1, keys=[/Data,111] 173 after: GetMultiWithoutTx #6, err=error by *glitchEmulator: GetMultiWithoutTx, keys=/Data,111 174 middleware/rpcretry.GetMultiWithoutTx: err=error by *glitchEmulator: GetMultiWithoutTx, keys=/Data,111, will be retry #2 after 2ns 175 after: GetMultiWithoutTx #7, len(keys)=1, keys=[/Data,111] 176 after: GetMultiWithoutTx #7, err=error by *glitchEmulator: GetMultiWithoutTx, keys=/Data,111 177 middleware/rpcretry.GetMultiWithoutTx: err=error by *glitchEmulator: GetMultiWithoutTx, keys=/Data,111, will be retry #3 after 4ns 178 after: GetMultiWithoutTx #8, len(keys)=1, keys=[/Data,111] 179 ## Delete. 180 before: DeleteMultiWithoutTx #3, len(keys)=1, keys=[/Data,111] 181 after: DeleteMultiWithoutTx #9, len(keys)=1, keys=[/Data,111] 182 after: DeleteMultiWithoutTx #9, err=error by *glitchEmulator: DeleteMultiWithoutTx, keys=/Data,111 183 middleware/rpcretry.DeleteMultiWithoutTx: err=error by *glitchEmulator: DeleteMultiWithoutTx, keys=/Data,111, will be retry #1 after 1ns 184 after: DeleteMultiWithoutTx #10, len(keys)=1, keys=[/Data,111] 185 after: DeleteMultiWithoutTx #10, err=error by *glitchEmulator: DeleteMultiWithoutTx, keys=/Data,111 186 middleware/rpcretry.DeleteMultiWithoutTx: err=error by *glitchEmulator: DeleteMultiWithoutTx, keys=/Data,111, will be retry #2 after 2ns 187 after: DeleteMultiWithoutTx #11, len(keys)=1, keys=[/Data,111] 188 after: DeleteMultiWithoutTx #11, err=error by *glitchEmulator: DeleteMultiWithoutTx, keys=/Data,111 189 middleware/rpcretry.DeleteMultiWithoutTx: err=error by *glitchEmulator: DeleteMultiWithoutTx, keys=/Data,111, will be retry #3 after 4ns 190 after: DeleteMultiWithoutTx #12, len(keys)=1, keys=[/Data,111] 191 `) 192 // strip `## FooBar` comment line 193 expected = regexp.MustCompile("(?m)^##.*\n").ReplaceAllString(expected, "") 194 195 if v := strings.Join(logs, "\n") + "\n"; v != expected { 196 t.Errorf("unexpected: %v", v) 197 } 198 } 199 200 func TestRPCRetry_ContextCanceled(t *testing.T) { 201 ctx, client, cleanUp := testutils.SetupCloudDatastore(t) 202 defer cleanUp() 203 204 var logs []string 205 logf := func(ctx context.Context, format string, args ...interface{}) { 206 t.Logf(format, args...) 207 logs = append(logs, fmt.Sprintf(format, args...)) 208 } 209 210 // setup. strategies are first in - first apply. 211 212 bLog := dslog.NewLogger("before: ", logf) 213 client.AppendMiddleware(bLog) 214 defer func() { 215 // stop logging before cleanUp func called. 216 client.RemoveMiddleware(bLog) 217 }() 218 219 rh := New( 220 WithLogger(logf), 221 WithMinBackoffDuration(1), 222 WithRetryLimit(2), 223 ) 224 client.AppendMiddleware(rh) 225 defer func() { 226 // stop logging before cleanUp func called. 227 client.RemoveMiddleware(rh) 228 }() 229 230 aLog := dslog.NewLogger("after: ", logf) 231 client.AppendMiddleware(aLog) 232 defer func() { 233 // stop logging before cleanUp func called. 234 client.RemoveMiddleware(aLog) 235 }() 236 237 type Data struct { 238 Name string 239 } 240 241 // Put. 242 canceledCtx, cancel := context.WithCancel(ctx) 243 cancel() 244 key := client.IDKey("Data", 111, nil) 245 objBefore := &Data{Name: "Data"} 246 _, err := client.Put(canceledCtx, key, objBefore) 247 if err == nil { 248 t.Fatal("expected an error") 249 } 250 251 expected := heredoc.Doc(` 252 before: PutMultiWithoutTx #1, len(keys)=1, keys=[/Data,111] 253 after: PutMultiWithoutTx #1, len(keys)=1, keys=[/Data,111] 254 after: PutMultiWithoutTx #1, err=rpc error: code = Canceled desc = context canceled 255 before: PutMultiWithoutTx #1, err=rpc error: code = Canceled desc = context canceled 256 `) 257 // strip `## FooBar` comment line 258 expected = regexp.MustCompile("(?m)^##.*\n").ReplaceAllString(expected, "") 259 260 if v := strings.Join(logs, "\n") + "\n"; v != expected { 261 t.Errorf("unexpected: %v", v) 262 } 263 } 264 265 func TestRPCRetry_Transaction(t *testing.T) { 266 ctx, client, cleanUp := testutils.SetupCloudDatastore(t) 267 defer cleanUp() 268 269 var logs []string 270 logf := func(ctx context.Context, format string, args ...interface{}) { 271 t.Logf(format, args...) 272 logs = append(logs, fmt.Sprintf(format, args...)) 273 } 274 275 // setup. strategies are first in - first apply. 276 277 bLog := dslog.NewLogger("before: ", logf) 278 client.AppendMiddleware(bLog) 279 defer func() { 280 // stop logging before cleanUp func called. 281 client.RemoveMiddleware(bLog) 282 }() 283 284 rh := New( 285 WithLogger(logf), 286 WithMinBackoffDuration(1), 287 WithRetryLimit(2), 288 ) 289 client.AppendMiddleware(rh) 290 defer func() { 291 // stop logging before cleanUp func called. 292 client.RemoveMiddleware(rh) 293 }() 294 295 aLog := dslog.NewLogger("after: ", logf) 296 client.AppendMiddleware(aLog) 297 defer func() { 298 // stop logging before cleanUp func called. 299 client.RemoveMiddleware(aLog) 300 }() 301 302 gm := &glitchEmulator{errCount: 1} 303 client.AppendMiddleware(gm) 304 defer func() { 305 // stop logging before cleanUp func called. 306 client.RemoveMiddleware(gm) 307 }() 308 309 // exec. 310 311 type Data struct { 312 Name string 313 } 314 315 key := client.NameKey("Data", "a", nil) 316 317 // put 318 _, err := client.Put(ctx, key, &Data{Name: "Before"}) 319 if err != nil { 320 t.Fatal(err) 321 } 322 323 { // Rollback 324 tx, err := client.NewTransaction(ctx) 325 if err != nil { 326 t.Fatal(err) 327 } 328 329 key2 := client.NameKey("Data", "b", nil) 330 _, err = tx.Put(key2, &Data{Name: "After"}) 331 if err != nil { 332 t.Fatal(err) 333 } 334 335 obj := &Data{} 336 err = tx.Get(key, obj) 337 if err != nil { 338 t.Fatal(err) 339 } 340 341 err = tx.Delete(key) 342 if err != nil { 343 t.Fatal(err) 344 } 345 346 // rollback. 347 err = tx.Rollback() 348 if err != nil { 349 t.Fatal(err) 350 } 351 } 352 353 // reset 354 gm.raised = nil 355 356 { // Commit 357 tx, err := client.NewTransaction(ctx) 358 if err != nil { 359 t.Fatal(err) 360 } 361 362 key2 := client.IncompleteKey("Data", nil) 363 pKey, err := tx.Put(key2, &Data{Name: "After"}) 364 if err != nil { 365 t.Fatal(err) 366 } 367 368 obj := &Data{} 369 err = tx.Get(key, obj) 370 if err != nil { 371 t.Fatal(err) 372 } 373 374 err = tx.Delete(key) 375 if err != nil { 376 t.Fatal(err) 377 } 378 379 // commit. 380 commit, err := tx.Commit() 381 if err != nil { 382 t.Fatal(err) 383 } 384 385 key3 := commit.Key(pKey) 386 if v := key3.Name(); v != key2.Name() { 387 t.Errorf("unexpected: %v", v) 388 } 389 // committed, but don't put to cache in tx. 390 } 391 392 var expected *regexp.Regexp 393 { 394 expectedPattern := heredoc.Doc(` 395 ## put 396 before: PutMultiWithoutTx #1, len(keys)=1, keys=[/Data,a] 397 after: PutMultiWithoutTx #1, len(keys)=1, keys=[/Data,a] 398 after: PutMultiWithoutTx #1, err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,a 399 middleware/rpcretry.PutMultiWithoutTx: err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,a, will be retry #1 after 1ns 400 after: PutMultiWithoutTx #2, len(keys)=1, keys=[/Data,a] 401 after: PutMultiWithoutTx #2, keys=[/Data,a] 402 before: PutMultiWithoutTx #1, keys=[/Data,a] 403 ## Rollback 404 ### put 405 before: PutMultiWithTx #2, len(keys)=1, keys=[/Data,b] 406 after: PutMultiWithTx #3, len(keys)=1, keys=[/Data,b] 407 after: PutMultiWithTx #3, err=error by *glitchEmulator: PutMultiWithTx, keys=/Data,b 408 after: PutMultiWithTx #3, keys length mismatch len(keys)=1, len(pKeys)=0 409 middleware/rpcretry.PutMultiWithTx: err=error by *glitchEmulator: PutMultiWithTx, keys=/Data,b, will be retry #1 after 1ns 410 after: PutMultiWithTx #4, len(keys)=1, keys=[/Data,b] 411 ### get 412 before: GetMultiWithTx #3, len(keys)=1, keys=[/Data,a] 413 after: GetMultiWithTx #5, len(keys)=1, keys=[/Data,a] 414 after: GetMultiWithTx #5, err=error by *glitchEmulator: GetMultiWithTx, keys=/Data,a 415 middleware/rpcretry.GetMultiWithTx: err=error by *glitchEmulator: GetMultiWithTx, keys=/Data,a, will be retry #1 after 1ns 416 after: GetMultiWithTx #6, len(keys)=1, keys=[/Data,a] 417 ### delete 418 before: DeleteMultiWithTx #4, len(keys)=1, keys=[/Data,a] 419 after: DeleteMultiWithTx #7, len(keys)=1, keys=[/Data,a] 420 after: DeleteMultiWithTx #7, err=error by *glitchEmulator: DeleteMultiWithTx, keys=/Data,a 421 middleware/rpcretry.DeleteMultiWithTx: err=error by *glitchEmulator: DeleteMultiWithTx, keys=/Data,a, will be retry #1 after 1ns 422 after: DeleteMultiWithTx #8, len(keys)=1, keys=[/Data,a] 423 ### rollback 424 before: PostRollback #5 425 after: PostRollback #9 426 ## Commit 427 ### put 428 before: PutMultiWithTx #6, len(keys)=1, keys=[/Data,0] 429 after: PutMultiWithTx #10, len(keys)=1, keys=[/Data,0] 430 after: PutMultiWithTx #10, err=error by *glitchEmulator: PutMultiWithTx, keys=/Data,0 431 after: PutMultiWithTx #10, keys length mismatch len(keys)=1, len(pKeys)=0 432 middleware/rpcretry.PutMultiWithTx: err=error by *glitchEmulator: PutMultiWithTx, keys=/Data,0, will be retry #1 after 1ns 433 after: PutMultiWithTx #11, len(keys)=1, keys=[/Data,0] 434 ### get 435 before: GetMultiWithTx #7, len(keys)=1, keys=[/Data,a] 436 after: GetMultiWithTx #12, len(keys)=1, keys=[/Data,a] 437 after: GetMultiWithTx #12, err=error by *glitchEmulator: GetMultiWithTx, keys=/Data,a 438 middleware/rpcretry.GetMultiWithTx: err=error by *glitchEmulator: GetMultiWithTx, keys=/Data,a, will be retry #1 after 1ns 439 after: GetMultiWithTx #13, len(keys)=1, keys=[/Data,a] 440 ### delete 441 before: DeleteMultiWithTx #8, len(keys)=1, keys=[/Data,a] 442 after: DeleteMultiWithTx #14, len(keys)=1, keys=[/Data,a] 443 after: DeleteMultiWithTx #14, err=error by *glitchEmulator: DeleteMultiWithTx, keys=/Data,a 444 middleware/rpcretry.DeleteMultiWithTx: err=error by *glitchEmulator: DeleteMultiWithTx, keys=/Data,a, will be retry #1 after 1ns 445 after: DeleteMultiWithTx #15, len(keys)=1, keys=[/Data,a] 446 ### commit 447 before: PostCommit #9 Put keys=[/Data,@####@] 448 after: PostCommit #16 Put keys=[/Data,@####@] 449 `) 450 // strip `## FooBar` comment line 451 expectedPattern = regexp.MustCompile("(?m)^##.*\n").ReplaceAllString(expectedPattern, "") 452 ss := strings.Split(expectedPattern, "@####@") 453 var buf bytes.Buffer 454 for idx, s := range ss { 455 buf.WriteString(regexp.QuoteMeta(s)) 456 if idx != (len(ss) - 1) { 457 buf.WriteString("[0-9]+") 458 } 459 } 460 expected = regexp.MustCompile(buf.String()) 461 } 462 463 if v := strings.Join(logs, "\n") + "\n"; !expected.MatchString(v) { 464 t.Errorf("unexpected: %v", v) 465 } 466 } 467 468 func TestRPCRetry_AllocateIDs(t *testing.T) { 469 ctx, client, cleanUp := testutils.SetupCloudDatastore(t) 470 defer cleanUp() 471 472 var logs []string 473 logf := func(ctx context.Context, format string, args ...interface{}) { 474 t.Logf(format, args...) 475 logs = append(logs, fmt.Sprintf(format, args...)) 476 } 477 478 // setup. strategies are first in - first apply. 479 480 bLog := dslog.NewLogger("before: ", logf) 481 client.AppendMiddleware(bLog) 482 defer func() { 483 // stop logging before cleanUp func called. 484 client.RemoveMiddleware(bLog) 485 }() 486 487 rh := New( 488 WithLogger(logf), 489 WithMinBackoffDuration(1), 490 WithRetryLimit(2), 491 ) 492 client.AppendMiddleware(rh) 493 defer func() { 494 // stop logging before cleanUp func called. 495 client.RemoveMiddleware(rh) 496 }() 497 498 aLog := dslog.NewLogger("after: ", logf) 499 client.AppendMiddleware(aLog) 500 defer func() { 501 // stop logging before cleanUp func called. 502 client.RemoveMiddleware(aLog) 503 }() 504 505 gm := &glitchEmulator{errCount: 1} 506 client.AppendMiddleware(gm) 507 defer func() { 508 // stop logging before cleanUp func called. 509 client.RemoveMiddleware(gm) 510 }() 511 512 // exec. 513 514 keys, err := client.AllocateIDs(ctx, []datastore.Key{client.IncompleteKey("Data", nil)}) 515 if err != nil { 516 t.Fatal(err) 517 } 518 519 if v := len(keys); v != 1 { 520 t.Fatalf("unexpected: %v", v) 521 } 522 if v := keys[0].Kind(); v != "Data" { 523 t.Errorf("unexpected: %v", v) 524 } 525 if v := keys[0].ID(); v == 0 { 526 t.Errorf("unexpected: %v", v) 527 } 528 529 expectedPattern := heredoc.Doc(` 530 before: AllocateIDs #1, len(keys)=1, keys=[/Data,0] 531 after: AllocateIDs #1, len(keys)=1, keys=[/Data,0] 532 after: AllocateIDs #1, err=error by *glitchEmulator: AllocateIDs, keys=/Data,0 533 middleware/rpcretry.AllocateIDs: err=error by *glitchEmulator: AllocateIDs, keys=/Data,0, will be retry #1 after 1ns 534 after: AllocateIDs #2, len(keys)=1, keys=[/Data,0] 535 after: AllocateIDs #2, keys=[/Data,@####@] 536 before: AllocateIDs #1, keys=[/Data,@####@] 537 `) 538 // strip `## FooBar` comment line 539 expectedPattern = regexp.MustCompile("(?m)^##.*\n").ReplaceAllString(expectedPattern, "") 540 ss := strings.Split(expectedPattern, "@####@") 541 var buf bytes.Buffer 542 for idx, s := range ss { 543 buf.WriteString(regexp.QuoteMeta(s)) 544 if idx != (len(ss) - 1) { 545 buf.WriteString("[0-9]+") 546 } 547 } 548 expected := regexp.MustCompile(buf.String()) 549 550 if v := strings.Join(logs, "\n") + "\n"; !expected.MatchString(v) { 551 t.Errorf("unexpected: %v", v) 552 } 553 } 554 555 func TestRPCRetry_GetAll(t *testing.T) { 556 ctx, client, cleanUp := testutils.SetupCloudDatastore(t) 557 defer cleanUp() 558 559 var logs []string 560 logf := func(ctx context.Context, format string, args ...interface{}) { 561 t.Logf(format, args...) 562 logs = append(logs, fmt.Sprintf(format, args...)) 563 } 564 565 // setup. strategies are first in - first apply. 566 567 bLog := dslog.NewLogger("before: ", logf) 568 client.AppendMiddleware(bLog) 569 defer func() { 570 // stop logging before cleanUp func called. 571 client.RemoveMiddleware(bLog) 572 }() 573 574 rh := New( 575 WithLogger(logf), 576 WithMinBackoffDuration(1), 577 WithRetryLimit(2), 578 ) 579 client.AppendMiddleware(rh) 580 defer func() { 581 // stop logging before cleanUp func called. 582 client.RemoveMiddleware(rh) 583 }() 584 585 aLog := dslog.NewLogger("after: ", logf) 586 client.AppendMiddleware(aLog) 587 defer func() { 588 // stop logging before cleanUp func called. 589 client.RemoveMiddleware(aLog) 590 }() 591 592 gm := &glitchEmulator{errCount: 1} 593 client.AppendMiddleware(gm) 594 defer func() { 595 // stop logging before cleanUp func called. 596 client.RemoveMiddleware(gm) 597 }() 598 599 // exec. 600 601 type Data struct { 602 Name string 603 } 604 605 const size = 3 606 607 keys := make([]datastore.Key, size) 608 list := make([]*Data, size) 609 for i := 0; i < size; i++ { 610 keys[i] = client.NameKey("Data", fmt.Sprintf("#%d", i+1), nil) 611 list[i] = &Data{ 612 Name: fmt.Sprintf("#%d", i+1), 613 } 614 } 615 _, err := client.PutMulti(ctx, keys, list) 616 if err != nil { 617 t.Fatal(err) 618 } 619 620 q := client.NewQuery("Data").Order("-Name") 621 622 // Run 623 iter := client.Run(ctx, q) 624 625 // Next 626 cnt := 0 627 for { 628 obj := &Data{} 629 key, err := iter.Next(obj) 630 if err == iterator.Done { 631 break 632 } else if err != nil { 633 t.Fatal(err) 634 } 635 if v := obj.Name; v == "" || v != key.Name() { 636 t.Errorf("unexpected: %v", cnt) 637 } 638 cnt++ 639 } 640 if cnt != size { 641 t.Errorf("unexpected: %v", cnt) 642 } 643 644 // GetAll 645 list = nil 646 _, err = client.GetAll(ctx, q, &list) 647 if err != nil { 648 t.Fatal(err) 649 } 650 651 expected := heredoc.Doc(` 652 ## Put 653 before: PutMultiWithoutTx #1, len(keys)=3, keys=[/Data,#1, /Data,#2, /Data,#3] 654 after: PutMultiWithoutTx #1, len(keys)=3, keys=[/Data,#1, /Data,#2, /Data,#3] 655 after: PutMultiWithoutTx #1, err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,#1, /Data,#2, /Data,#3 656 middleware/rpcretry.PutMultiWithoutTx: err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,#1, /Data,#2, /Data,#3, will be retry #1 after 1ns 657 after: PutMultiWithoutTx #2, len(keys)=3, keys=[/Data,#1, /Data,#2, /Data,#3] 658 after: PutMultiWithoutTx #2, keys=[/Data,#1, /Data,#2, /Data,#3] 659 before: PutMultiWithoutTx #1, keys=[/Data,#1, /Data,#2, /Data,#3] 660 ## Run & Next 661 before: Run #2, q=v1:Data&or=-Name 662 after: Run #3, q=v1:Data&or=-Name 663 before: Next #3, q=v1:Data&or=-Name 664 after: Next #4, q=v1:Data&or=-Name 665 after: Next #4, key=/Data,#3 666 before: Next #3, key=/Data,#3 667 before: Next #4, q=v1:Data&or=-Name 668 after: Next #5, q=v1:Data&or=-Name 669 after: Next #5, key=/Data,#2 670 before: Next #4, key=/Data,#2 671 before: Next #5, q=v1:Data&or=-Name 672 after: Next #6, q=v1:Data&or=-Name 673 after: Next #6, key=/Data,#1 674 before: Next #5, key=/Data,#1 675 before: Next #6, q=v1:Data&or=-Name 676 after: Next #7, q=v1:Data&or=-Name 677 after: Next #7, err=no more items in iterator 678 before: Next #6, err=no more items in iterator 679 ## GetAll 680 before: GetAll #7, q=v1:Data&or=-Name 681 after: GetAll #8, q=v1:Data&or=-Name 682 after: GetAll #8, err=error by *glitchEmulator: GetAll, keys= 683 middleware/rpcretry.GetAll: err=error by *glitchEmulator: GetAll, keys=, will be retry #1 after 1ns 684 after: GetAll #9, q=v1:Data&or=-Name 685 after: GetAll #9, len(keys)=3, keys=[/Data,#3, /Data,#2, /Data,#1] 686 before: GetAll #7, len(keys)=3, keys=[/Data,#3, /Data,#2, /Data,#1] 687 `) 688 // strip `## FooBar` comment line 689 expected = regexp.MustCompile("(?m)^##.*\n").ReplaceAllString(expected, "") 690 691 if v := strings.Join(logs, "\n") + "\n"; v != expected { 692 t.Errorf("unexpected: %v", v) 693 } 694 } 695 696 func TestRPCRetry_Count(t *testing.T) { 697 ctx, client, cleanUp := testutils.SetupCloudDatastore(t) 698 defer cleanUp() 699 700 var logs []string 701 logf := func(ctx context.Context, format string, args ...interface{}) { 702 t.Logf(format, args...) 703 logs = append(logs, fmt.Sprintf(format, args...)) 704 } 705 706 // setup. strategies are first in - first apply. 707 708 bLog := dslog.NewLogger("before: ", logf) 709 client.AppendMiddleware(bLog) 710 defer func() { 711 // stop logging before cleanUp func called. 712 client.RemoveMiddleware(bLog) 713 }() 714 715 rh := New( 716 WithLogger(logf), 717 WithMinBackoffDuration(1), 718 WithRetryLimit(2), 719 ) 720 client.AppendMiddleware(rh) 721 defer func() { 722 // stop logging before cleanUp func called. 723 client.RemoveMiddleware(rh) 724 }() 725 726 aLog := dslog.NewLogger("after: ", logf) 727 client.AppendMiddleware(aLog) 728 defer func() { 729 // stop logging before cleanUp func called. 730 client.RemoveMiddleware(aLog) 731 }() 732 733 gm := &glitchEmulator{errCount: 1} 734 client.AppendMiddleware(gm) 735 defer func() { 736 // stop logging before cleanUp func called. 737 client.RemoveMiddleware(gm) 738 }() 739 740 // exec. 741 742 type Data struct { 743 Name string 744 } 745 746 const size = 3 747 748 keys := make([]datastore.Key, size) 749 list := make([]*Data, size) 750 for i := 0; i < size; i++ { 751 keys[i] = client.NameKey("Data", fmt.Sprintf("#%d", i+1), nil) 752 list[i] = &Data{ 753 Name: fmt.Sprintf("#%d", i+1), 754 } 755 } 756 _, err := client.PutMulti(ctx, keys, list) 757 if err != nil { 758 t.Fatal(err) 759 } 760 761 q := client.NewQuery("Data") 762 763 cnt, err := client.Count(ctx, q) 764 if err != nil { 765 t.Fatal(err) 766 } 767 768 if cnt != size { 769 t.Errorf("unexpected: %v", cnt) 770 } 771 772 expected := heredoc.Doc(` 773 ## Put 774 before: PutMultiWithoutTx #1, len(keys)=3, keys=[/Data,#1, /Data,#2, /Data,#3] 775 after: PutMultiWithoutTx #1, len(keys)=3, keys=[/Data,#1, /Data,#2, /Data,#3] 776 after: PutMultiWithoutTx #1, err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,#1, /Data,#2, /Data,#3 777 middleware/rpcretry.PutMultiWithoutTx: err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,#1, /Data,#2, /Data,#3, will be retry #1 after 1ns 778 after: PutMultiWithoutTx #2, len(keys)=3, keys=[/Data,#1, /Data,#2, /Data,#3] 779 after: PutMultiWithoutTx #2, keys=[/Data,#1, /Data,#2, /Data,#3] 780 before: PutMultiWithoutTx #1, keys=[/Data,#1, /Data,#2, /Data,#3] 781 ## Count 782 before: Count #2, q=v1:Data 783 after: Count #3, q=v1:Data 784 after: Count #3, err=error by *glitchEmulator: Count, keys= 785 middleware/rpcretry.Count: err=error by *glitchEmulator: Count, keys=, will be retry #1 after 1ns 786 after: Count #4, q=v1:Data 787 after: Count #4, ret=3 788 before: Count #2, ret=3 789 `) 790 // strip `## FooBar` comment line 791 expected = regexp.MustCompile("(?m)^##.*\n").ReplaceAllString(expected, "") 792 793 if v := strings.Join(logs, "\n") + "\n"; v != expected { 794 t.Errorf("unexpected: %v", v) 795 } 796 }