@@ -125,7 +125,6 @@ def test_state_machine_w_state(self):
125125 ("start" , (), (State .CREATED ,), None ),
126126 ("start_attempt" , (), (State .CREATED , State .BETWEEN_ATTEMPTS ), None ),
127127 ("add_response_metadata" , ({},), (State .ACTIVE_ATTEMPT ,), None ),
128- ("attempt_first_response" , (), (State .ACTIVE_ATTEMPT ,), None ),
129128 ("end_attempt_with_status" , (mock .Mock (),), (State .ACTIVE_ATTEMPT ,), None ),
130129 (
131130 "end_with_status" ,
@@ -202,7 +201,6 @@ def test_start_attempt(self):
202201 assert (
203202 abs (time .monotonic_ns () - metric .active_attempt .start_time_ns ) < 1e6
204203 ) # 1ms buffer
205- assert metric .active_attempt .first_response_latency_ns is None
206204 assert metric .active_attempt .gfe_latency_ns is None
207205 assert metric .active_attempt .grpc_throttling_time_ns == 0
208206 # should be in ACTIVE_ATTEMPT state after completing
@@ -219,8 +217,6 @@ def test_start_attempt_with_backoff_generator(self):
219217 # pre-seed generator with exepcted values
220218 generator .history = list (range (10 ))
221219 metric = self ._make_one (mock .Mock (), backoff_generator = generator )
222- # initialize generator
223- next (metric .backoff_generator )
224220 metric .start_attempt ()
225221 assert len (metric .completed_attempts ) == 0
226222 # first attempt should always be 0
@@ -307,7 +303,7 @@ def test_add_response_metadata_cbt_header(
307303 @pytest .mark .parametrize (
308304 "metadata_field" ,
309305 [
310- b"cluster " ,
306+ b"bad-input " ,
311307 "cluster zone" , # expect bytes
312308 ],
313309 )
@@ -389,38 +385,13 @@ def test_add_response_metadata_server_timing_header(
389385 assert metric .cluster_id is None
390386 assert metric .zone is None
391387
392- def test_attempt_first_response (self ):
393- cls = type (self ._make_one (mock .Mock ()))
394- with mock .patch .object (cls , "_handle_error" ) as mock_handle_error :
395- metric = self ._make_one (mock .Mock ())
396- metric .start_attempt ()
397- metric .active_attempt .start_time_ns = 0
398- metric .attempt_first_response ()
399- got_latency_ns = metric .active_attempt .first_response_latency_ns
400- # latency should be equal to current time
401- assert abs (got_latency_ns - time .monotonic_ns ()) < 1e6 # 1ms
402- # should remain in ACTIVE_ATTEMPT state after completing
403- assert metric .state == State .ACTIVE_ATTEMPT
404- # no errors encountered
405- assert mock_handle_error .call_count == 0
406- # calling it again should cause an error
407- metric .attempt_first_response ()
408- assert mock_handle_error .call_count == 1
409- assert (
410- mock_handle_error .call_args [0 ][0 ]
411- == "Attempt already received first response"
412- )
413- # value should not be changed
414- assert metric .active_attempt .first_response_latency_ns == got_latency_ns
415-
416388 def test_end_attempt_with_status (self ):
417389 """
418390 ending the attempt should:
419391 - add one to completed_attempts
420392 - reset active_attempt to None
421393 - update state
422394 """
423- expected_latency_ns = 9
424395 expected_start_time = 1
425396 expected_status = object ()
426397 expected_gfe_latency_ns = 5
@@ -434,7 +405,6 @@ def test_end_attempt_with_status(self):
434405 metric .start_attempt ()
435406 metric .active_attempt .start_time_ns = expected_start_time
436407 metric .active_attempt .gfe_latency_ns = expected_gfe_latency_ns
437- metric .active_attempt .first_response_latency_ns = expected_latency_ns
438408 metric .active_attempt .application_blocking_time_ns = expected_app_blocking
439409 metric .active_attempt .backoff_before_attempt_ns = expected_backoff
440410 metric .active_attempt .grpc_throttling_time_ns = expected_grpc_throttle
@@ -443,7 +413,6 @@ def test_end_attempt_with_status(self):
443413 got_attempt = metric .completed_attempts [0 ]
444414 expected_duration = time .monotonic_ns () - expected_start_time
445415 assert abs (got_attempt .duration_ns - expected_duration ) < 10e6 # within 10ms
446- assert got_attempt .first_response_latency_ns == expected_latency_ns
447416 assert got_attempt .grpc_throttling_time_ns == expected_grpc_throttle
448417 assert got_attempt .end_status == expected_status
449418 assert got_attempt .gfe_latency_ns == expected_gfe_latency_ns
@@ -479,10 +448,10 @@ def test_end_with_status(self):
479448 from google .cloud .bigtable .data ._metrics .data_model import ActiveAttemptMetric
480449
481450 expected_attempt_start_time = 0
482- expected_attempt_first_response_latency_ns = 9
483451 expected_attempt_gfe_latency_ns = 5
484452 expected_flow_time = 16
485453
454+ expected_first_response_latency_ns = 9
486455 expected_status = object ()
487456 expected_type = object ()
488457 expected_start_time = 1
@@ -498,9 +467,9 @@ def test_end_with_status(self):
498467 metric .zone = expected_zone
499468 metric .is_streaming = is_streaming
500469 metric .flow_throttling_time_ns = expected_flow_time
470+ metric .first_response_latency_ns = expected_first_response_latency_ns
501471 attempt = ActiveAttemptMetric (
502472 start_time_ns = expected_attempt_start_time ,
503- first_response_latency_ns = expected_attempt_first_response_latency_ns ,
504473 gfe_latency_ns = expected_attempt_gfe_latency_ns ,
505474 )
506475 metric .active_attempt = attempt
@@ -525,13 +494,13 @@ def test_end_with_status(self):
525494 assert called_with .zone == expected_zone
526495 assert called_with .is_streaming == is_streaming
527496 assert called_with .flow_throttling_time_ns == expected_flow_time
497+ assert (
498+ called_with .first_response_latency_ns
499+ == expected_first_response_latency_ns
500+ )
528501 # check the attempt
529502 assert len (called_with .completed_attempts ) == 1
530503 final_attempt = called_with .completed_attempts [0 ]
531- assert (
532- final_attempt .first_response_latency_ns
533- == expected_attempt_first_response_latency_ns
534- )
535504 assert final_attempt .gfe_latency_ns == expected_attempt_gfe_latency_ns
536505 assert final_attempt .end_status == expected_status
537506 expected_duration = time .monotonic_ns () - expected_attempt_start_time
@@ -590,32 +559,6 @@ def test_end_on_empty_operation(self):
590559 assert final_op .final_status == StatusCode .OK
591560 assert final_op .completed_attempts == []
592561
593- def test_build_wrapped_predicate (self ):
594- """
595- predicate generated by object should terminate attempt or operation
596- based on passed in predicate
597- """
598- input_exc = ValueError ("test" )
599- cls = type (self ._make_one (object ()))
600- # ensure predicate is called with the exception
601- mock_predicate = mock .Mock ()
602- cls .build_wrapped_predicate (mock .Mock (), mock_predicate )(input_exc )
603- assert mock_predicate .call_count == 1
604- assert mock_predicate .call_args [0 ][0 ] == input_exc
605- assert len (mock_predicate .call_args [0 ]) == 1
606- # if predicate is true, end the attempt
607- mock_instance = mock .Mock ()
608- cls .build_wrapped_predicate (mock_instance , lambda x : True )(input_exc )
609- assert mock_instance .end_attempt_with_status .call_count == 1
610- assert mock_instance .end_attempt_with_status .call_args [0 ][0 ] == input_exc
611- assert len (mock_instance .end_attempt_with_status .call_args [0 ]) == 1
612- # if predicate is false, end the operation
613- mock_instance = mock .Mock ()
614- cls .build_wrapped_predicate (mock_instance , lambda x : False )(input_exc )
615- assert mock_instance .end_with_status .call_count == 1
616- assert mock_instance .end_with_status .call_args [0 ][0 ] == input_exc
617- assert len (mock_instance .end_with_status .call_args [0 ]) == 1
618-
619562 def test__exc_to_status (self ):
620563 """
621564 Should return grpc_status_code if grpc error, otherwise UNKNOWN
@@ -688,16 +631,15 @@ def test__handle_error(self):
688631 assert len (logger_mock .warning .call_args [0 ]) == 1
689632
690633 @pytest .mark .asyncio
691- async def test_async_context_manager (self ):
634+ async def test_context_manager (self ):
692635 """
693636 Should implement context manager protocol
694637 """
695638 metric = self ._make_one (object ())
696639 with mock .patch .object (metric , "end_with_success" ) as end_with_success_mock :
697640 end_with_success_mock .side_effect = lambda : metric .end_with_status (object ())
698- async with metric as context :
699- assert isinstance (context , type (metric )._AsyncContextManager )
700- assert context .operation == metric
641+ with metric as context :
642+ assert context == metric
701643 # inside context manager, still active
702644 assert end_with_success_mock .call_count == 0
703645 assert metric .state == State .CREATED
@@ -706,17 +648,15 @@ async def test_async_context_manager(self):
706648 assert metric .state == State .COMPLETED
707649
708650 @pytest .mark .asyncio
709- async def test_async_context_manager_exception (self ):
651+ async def test_context_manager_exception (self ):
710652 """
711653 Exception within context manager causes end_with_status to be called with error
712654 """
713655 expected_exc = ValueError ("expected" )
714656 metric = self ._make_one (object ())
715657 with mock .patch .object (metric , "end_with_status" ) as end_with_status_mock :
716658 try :
717- async with metric as context :
718- assert isinstance (context , type (metric )._AsyncContextManager )
719- assert context .operation == metric
659+ with metric :
720660 # inside context manager, still active
721661 assert end_with_status_mock .call_count == 0
722662 assert metric .state == State .CREATED
@@ -726,155 +666,3 @@ async def test_async_context_manager_exception(self):
726666 # outside context manager, should be ended
727667 assert end_with_status_mock .call_count == 1
728668 assert end_with_status_mock .call_args [0 ][0 ] == expected_exc
729- assert len (end_with_status_mock .call_args [0 ]) == 1
730-
731- @pytest .mark .asyncio
732- async def test_metadata_passthrough (self ):
733- """
734- add_response_metadata in context manager should defer to wrapped operation
735- """
736- inner_result = object ()
737- fake_metadata = object ()
738-
739- metric = self ._make_one (mock .Mock ())
740- with mock .patch .object (metric , "add_response_metadata" ) as mock_add_metadata :
741- mock_add_metadata .return_value = inner_result
742- async with metric as context :
743- result = context .add_response_metadata (fake_metadata )
744- assert result == inner_result
745- assert mock_add_metadata .call_count == 1
746- assert mock_add_metadata .call_args [0 ][0 ] == fake_metadata
747- assert len (mock_add_metadata .call_args [0 ]) == 1
748-
749- @pytest .mark .asyncio
750- async def test_wrap_attempt_fn_success (self ):
751- """
752- Context manager's wrap_attempt_fn should wrap an arbitrary function
753- in operation instrumentation
754-
755- Test successful call
756- - should return the result of the wrapped function
757- - should call end_with_success
758- """
759- from grpc import StatusCode
760-
761- metric = self ._make_one (object ())
762- async with metric as context :
763- mock_call = mock .AsyncMock ()
764- mock_args = (1 , 2 , 3 )
765- mock_kwargs = {"a" : 1 , "b" : 2 }
766- inner_fn = lambda * args , ** kwargs : mock_call (* args , ** kwargs ) # noqa
767- wrapped_fn = context .wrap_attempt_fn (inner_fn , extract_call_metadata = False )
768- # make the wrapped call
769- result = await wrapped_fn (* mock_args , ** mock_kwargs )
770- assert result == mock_call .return_value
771- assert mock_call .call_count == 1
772- assert mock_call .call_args [0 ] == mock_args
773- assert mock_call .call_args [1 ] == mock_kwargs
774- assert mock_call .await_count == 1
775- # operation should be still in progress after wrapped fn
776- # let context manager close it, in case we need to add metadata, etc
777- assert metric .state == State .ACTIVE_ATTEMPT
778- # make sure the operation is complete after exiting context manager
779- assert metric .state == State .COMPLETED
780- assert len (metric .completed_attempts ) == 1
781- assert metric .completed_attempts [0 ].end_status == StatusCode .OK
782-
783- @pytest .mark .asyncio
784- async def test_wrap_attempt_fn_failed_extract_call_metadata (self ):
785- """
786- When extract_call_metadata is True, should call add_response_metadata
787- on operation with output of wrapped function, even if failed
788- """
789- mock_call = mock .AsyncMock ()
790- mock_call .trailing_metadata .return_value = 3
791- mock_call .initial_metadata .return_value = 4
792- inner_fn = lambda * args , ** kwargs : mock_call # noqa
793- metric = self ._make_one (object ())
794- async with metric as context :
795- wrapped_fn = context .wrap_attempt_fn (inner_fn , extract_call_metadata = True )
796- with mock .patch .object (
797- metric , "add_response_metadata"
798- ) as mock_add_metadata :
799- # make the wrapped call. expect exception when awaiting on mock_call
800- with pytest .raises (TypeError ):
801- await wrapped_fn ()
802- assert mock_add_metadata .call_count == 1
803- assert mock_call .trailing_metadata .call_count == 1
804- assert mock_call .initial_metadata .call_count == 1
805- assert mock_add_metadata .call_args [0 ][0 ] == 3 + 4
806-
807- @pytest .mark .asyncio
808- async def test_wrap_attempt_fn_failed_extract_call_metadata_no_mock (self ):
809- """
810- Make sure the metadata is accessible after a failed attempt
811- """
812- import grpc
813-
814- mock_call = mock .AsyncMock ()
815- mock_call .trailing_metadata .return_value = grpc .aio .Metadata ()
816- mock_call .initial_metadata .return_value = grpc .aio .Metadata (
817- ("server-timing" , "gfet4t7; dur=5000" )
818- )
819- inner_fn = lambda * args , ** kwargs : mock_call # noqa
820- metric = self ._make_one (object ())
821- async with metric as context :
822- wrapped_fn = context .wrap_attempt_fn (inner_fn , extract_call_metadata = True )
823- with pytest .raises (TypeError ):
824- await wrapped_fn ()
825- assert metric .active_attempt is None
826- assert len (metric .completed_attempts ) == 1
827- assert metric .completed_attempts [0 ].gfe_latency_ns == 5000e6 # ms to ns
828-
829- @pytest .mark .asyncio
830- async def test_wrap_attempt_fn_failed_attempt (self ):
831- """
832- failed attempts should call operation.end_attempt with error
833- """
834- from grpc import StatusCode
835-
836- metric = self ._make_one (object ())
837- async with metric as context :
838- wrapped_fn = context .wrap_attempt_fn (
839- mock .Mock (), extract_call_metadata = False
840- )
841- # make the wrapped call. expect type error when awaiting response of mock
842- with pytest .raises (TypeError ):
843- await wrapped_fn ()
844- # should have one failed attempt, but operation still in progress
845- assert len (metric .completed_attempts ) == 1
846- assert metric .state == State .BETWEEN_ATTEMPTS
847- assert metric .active_attempt is None
848- # unknown status from type error
849- assert metric .completed_attempts [0 ].end_status == StatusCode .UNKNOWN
850- # make sure operation is closed on end
851- assert metric .state == State .COMPLETED
852-
853- @pytest .mark .asyncio
854- async def test_wrap_attempt_fn_with_retry (self ):
855- """
856- wrap_attampt_fn is meant to be used with retry object. Test using them together
857- """
858- from grpc import StatusCode
859- from google .api_core .retry import AsyncRetry
860- from google .api_core .exceptions import RetryError
861-
862- metric = self ._make_one (object ())
863- with pytest .raises (RetryError ):
864- # should eventually fail due to timeout
865- async with metric as context :
866- always_retry = lambda x : True # noqa
867- retry_obj = AsyncRetry (
868- predicate = always_retry , timeout = 0.05 , maximum = 0.001
869- )
870- # mock.Mock will fail on await
871- double_wrapped_fn = retry_obj (
872- context .wrap_attempt_fn (mock .Mock (), extract_call_metadata = False )
873- )
874- await double_wrapped_fn ()
875- # make sure operation ended with expected state
876- assert metric .state == State .COMPLETED
877- # we expect > 30 retries in 0.05 seconds
878- assert len (metric .completed_attempts ) > 5
879- # unknown error due to TyperError
880- assert metric .completed_attempts [- 1 ].end_status == StatusCode .UNKNOWN
0 commit comments