@@ -477,6 +477,97 @@ func (s) TestRetryStreaming(t *testing.T) {
477477 }
478478}
479479
480+ func (s ) TestMaxCallAttempts (t * testing.T ) {
481+ testCases := []struct {
482+ serviceMaxAttempts int
483+ clientMaxAttempts int
484+ expectedAttempts int
485+ }{
486+ {serviceMaxAttempts : 9 , clientMaxAttempts : 4 , expectedAttempts : 4 },
487+ {serviceMaxAttempts : 9 , clientMaxAttempts : 7 , expectedAttempts : 7 },
488+ {serviceMaxAttempts : 3 , clientMaxAttempts : 10 , expectedAttempts : 3 },
489+ {serviceMaxAttempts : 8 , clientMaxAttempts : - 1 , expectedAttempts : 5 }, // 5 is default max
490+ {serviceMaxAttempts : 3 , clientMaxAttempts : 0 , expectedAttempts : 3 },
491+ }
492+
493+ for _ , tc := range testCases {
494+ clientOpts := []grpc.DialOption {
495+ grpc .WithMaxCallAttempts (tc .clientMaxAttempts ),
496+ grpc .WithDefaultServiceConfig (fmt .Sprintf (`{
497+ "methodConfig": [{
498+ "name": [{"service": "grpc.testing.TestService"}],
499+ "waitForReady": true,
500+ "retryPolicy": {
501+ "MaxAttempts": %d,
502+ "InitialBackoff": ".01s",
503+ "MaxBackoff": ".01s",
504+ "BackoffMultiplier": 1.0,
505+ "RetryableStatusCodes": [ "UNAVAILABLE" ]
506+ }
507+ }]}` , tc .serviceMaxAttempts ),
508+ ),
509+ }
510+
511+ streamCallCount := 0
512+ unaryCallCount := 0
513+
514+ ss := & stubserver.StubServer {
515+ FullDuplexCallF : func (stream testgrpc.TestService_FullDuplexCallServer ) error {
516+ streamCallCount ++
517+ return status .New (codes .Unavailable , "this is a test error" ).Err ()
518+ },
519+ EmptyCallF : func (context.Context , * testpb.Empty ) (r * testpb.Empty , err error ) {
520+ unaryCallCount ++
521+ return nil , status .New (codes .Unavailable , "this is a test error" ).Err ()
522+ },
523+ }
524+
525+ func () {
526+
527+ if err := ss .Start ([]grpc.ServerOption {}, clientOpts ... ); err != nil {
528+ t .Fatalf ("Error starting endpoint server: %v" , err )
529+ }
530+ defer ss .Stop ()
531+ ctx , cancel := context .WithTimeout (context .Background (), defaultTestTimeout )
532+ defer cancel ()
533+
534+ for {
535+ if ctx .Err () != nil {
536+ t .Fatalf ("Timed out waiting for service config update" )
537+ }
538+ if ss .CC .GetMethodConfig ("/grpc.testing.TestService/FullDuplexCall" ).WaitForReady != nil {
539+ break
540+ }
541+ time .Sleep (time .Millisecond )
542+ }
543+
544+ // Test streaming RPC
545+ stream , err := ss .Client .FullDuplexCall (ctx )
546+ if err != nil {
547+ t .Fatalf ("Error while creating stream: %v" , err )
548+ }
549+ if got , err := stream .Recv (); err == nil {
550+ t .Fatalf ("client: Recv() = %s, %v; want <nil>, error" , got , err )
551+ } else if status .Code (err ) != codes .Unavailable {
552+ t .Fatalf ("client: Recv() = _, %v; want _, Unavailable" , err )
553+ }
554+ if streamCallCount != tc .expectedAttempts {
555+ t .Fatalf ("stream expectedAttempts = %v; want %v" , streamCallCount , tc .expectedAttempts )
556+ }
557+
558+ // Test unary RPC
559+ if ugot , err := ss .Client .EmptyCall (ctx , & testpb.Empty {}); err == nil {
560+ t .Fatalf ("client: EmptyCall() = %s, %v; want <nil>, error" , ugot , err )
561+ } else if status .Code (err ) != codes .Unavailable {
562+ t .Fatalf ("client: EmptyCall() = _, %v; want _, Unavailable" , err )
563+ }
564+ if unaryCallCount != tc .expectedAttempts {
565+ t .Fatalf ("unary expectedAttempts = %v; want %v" , unaryCallCount , tc .expectedAttempts )
566+ }
567+ }()
568+ }
569+ }
570+
480571type retryStatsHandler struct {
481572 mu sync.Mutex
482573 s []stats.RPCStats
0 commit comments