Skip to content
This repository was archived by the owner on Apr 1, 2026. It is now read-only.

Commit 708a35a

Browse files
committed
fixed broken unit tests
1 parent 5ea9f0e commit 708a35a

4 files changed

Lines changed: 42 additions & 238 deletions

File tree

google/cloud/bigtable/data/_helpers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,4 +277,6 @@ def get_attempt_backoff(self, attempt_idx) -> float:
277277
"""
278278
returns the backoff time for a specific attempt index, starting at 0.
279279
"""
280+
if attempt_idx < 0:
281+
raise IndexError("received negative attempt number")
280282
return self.history[attempt_idx]

google/cloud/bigtable/data/_metrics/metrics_controller.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ def create_operation(
7272
"""
7373
Creates a new operation and registers it with the subscribed handlers.
7474
"""
75-
handlers = self.handlers + kwargs.pop("handlers", [])
76-
new_op = ActiveOperationMetric(op_type, **kwargs, handlers=handlers)
75+
new_op = ActiveOperationMetric(op_type, **kwargs, handlers=self.handlers)
7776
self.interceptor.register_operation(new_op)
7877
return new_op

tests/unit/data/_metrics/test_data_model.py

Lines changed: 12 additions & 224 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)