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

Commit 761cf9c

Browse files
committed
added tests for sample_row_keys
1 parent eb8a229 commit 761cf9c

1 file changed

Lines changed: 165 additions & 17 deletions

File tree

tests/system/data/test_metrics_async.py

Lines changed: 165 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
from grpc.aio import AioRpcError
2020
from grpc.aio import Metadata
2121

22+
from google.api_core.exceptions import Aborted
2223
from google.api_core.exceptions import GoogleAPICallError
24+
from google.api_core.exceptions import PermissionDenied
2325
from google.cloud.bigtable_v2.types import ResponseParams
2426
from google.cloud.bigtable.data._metrics.handlers._base import MetricsHandler
2527
from google.cloud.bigtable.data._metrics.data_model import CompletedOperationMetric, CompletedAttemptMetric, ActiveOperationMetric, OperationState
@@ -66,19 +68,21 @@ def __repr__(self):
6668
return f"{self.__class__}(completed_operations={len(self.completed_operations)}, cancelled_operations={len(self.cancelled_operations)}, completed_attempts={len(self.completed_attempts)}"
6769

6870

69-
class _ErrorInjectorInterceptor(UnaryUnaryClientInterceptor):
71+
class _ErrorInjectorInterceptor(UnaryUnaryClientInterceptor, UnaryStreamClientInterceptor):
7072
"""
7173
Gprc interceptor used to inject errors into rpc calls, to test failures
7274
"""
7375

7476
def __init__(self):
7577
self._exc_list = []
78+
self.fail_mid_stream = False
7679

7780
def push(self, exc: Exception):
7881
self._exc_list.append(exc)
7982

8083
def clear(self):
8184
self._exc_list.clear()
85+
self.fail_mid_stream = False
8286

8387
async def intercept_unary_unary(
8488
self, continuation, client_call_details, request
@@ -87,6 +91,41 @@ async def intercept_unary_unary(
8791
raise self._exc_list.pop(0)
8892
return await continuation(client_call_details, request)
8993

94+
async def intercept_unary_stream(
95+
self, continuation, client_call_details, request
96+
):
97+
if not self.fail_mid_stream and self._exc_list:
98+
raise self._exc_list.pop(0)
99+
100+
response = await continuation(client_call_details, request)
101+
102+
if self.fail_mid_stream and self._exc_list:
103+
exc = self._exc_list.pop(0)
104+
105+
class CallWrapper:
106+
def __init__(self, call, exc_to_raise):
107+
self._call = call
108+
self._exc = exc_to_raise
109+
self._raised = False
110+
111+
def __aiter__(self):
112+
return self
113+
114+
async def __anext__(self):
115+
if not self._raised:
116+
self._raised = True
117+
if self._exc:
118+
raise self._exc
119+
return await self._call.__anext__()
120+
121+
def __getattr__(self, name):
122+
return getattr(self._call, name)
123+
124+
return CallWrapper(response, exc)
125+
126+
return response
127+
128+
90129
@CrossSync.convert_class(sync_name="TestMetrics")
91130
class TestMetricsAsync(SystemTestRunner):
92131

@@ -123,6 +162,9 @@ async def client(self, error_injector):
123162
async with self._make_client() as client:
124163
if CrossSync.is_async:
125164
client.transport.grpc_channel._unary_unary_interceptors.append(error_injector)
165+
client.transport.grpc_channel._unary_stream_interceptors.append(
166+
error_injector
167+
)
126168
yield client
127169

128170
@CrossSync.convert
@@ -679,41 +721,147 @@ async def test_sample_row_keys_failure_grpc(
679721
self, table, temp_rows, handler, error_injector
680722
):
681723
"""
682-
Test failure in grpc layer by injecting an error into an interceptor
724+
Test failure in grpc layer by injecting errors into an interceptor
725+
test with retryable errors, then a terminal one
726+
727+
No headers expected
728+
"""
729+
exc = Aborted("injected")
730+
num_retryable = 3
731+
for i in range(num_retryable):
732+
error_injector.push(exc)
733+
error_injector.push(RuntimeError)
734+
with pytest.raises(RuntimeError):
735+
await table.sample_row_keys(retryable_errors=[Aborted])
736+
# validate counts
737+
assert len(handler.completed_operations) == 1
738+
assert len(handler.completed_attempts) == num_retryable+1
739+
assert len(handler.cancelled_operations) == 0
740+
# validate operation
741+
operation = handler.completed_operations[0]
742+
assert isinstance(operation, CompletedOperationMetric)
743+
assert operation.final_status.name == "UNKNOWN"
744+
assert operation.op_type.value == "SampleRowKeys"
745+
assert operation.is_streaming is False
746+
assert len(operation.completed_attempts) == num_retryable+1
747+
assert operation.completed_attempts[0] == handler.completed_attempts[0]
748+
assert operation.cluster_id == "unspecified"
749+
assert operation.zone == "global"
750+
# validate attempts
751+
for i in range(num_retryable):
752+
attempt = handler.completed_attempts[i]
753+
assert isinstance(attempt, CompletedAttemptMetric)
754+
assert attempt.end_status.name == "ABORTED"
755+
assert attempt.gfe_latency_ns is None
756+
final_attempt = handler.completed_attempts[num_retryable]
757+
assert isinstance(final_attempt, CompletedAttemptMetric)
758+
assert final_attempt.end_status.name == "UNKNOWN"
759+
assert final_attempt.gfe_latency_ns is None
683760

761+
@CrossSync.pytest
762+
async def test_sample_row_keys_failure_grpc_eventual_success(
763+
self, table, temp_rows, handler, error_injector, cluster_config
764+
):
765+
"""
766+
Test failure in grpc layer by injecting errors into an interceptor
767+
test with retryable errors, then a success
768+
684769
No headers expected
685770
"""
686-
pass
771+
exc = Aborted("injected")
772+
num_retryable = 3
773+
for i in range(num_retryable):
774+
error_injector.push(exc)
775+
await table.sample_row_keys(retryable_errors=[Aborted])
776+
# validate counts
777+
assert len(handler.completed_operations) == 1
778+
assert len(handler.completed_attempts) == num_retryable+1
779+
assert len(handler.cancelled_operations) == 0
780+
# validate operation
781+
operation = handler.completed_operations[0]
782+
assert isinstance(operation, CompletedOperationMetric)
783+
assert operation.final_status.name == "OK"
784+
assert operation.op_type.value == "SampleRowKeys"
785+
assert operation.is_streaming is False
786+
assert len(operation.completed_attempts) == num_retryable+1
787+
assert operation.completed_attempts[0] == handler.completed_attempts[0]
788+
assert operation.cluster_id == next(iter(cluster_config.keys()))
789+
assert operation.zone == cluster_config[operation.cluster_id].location.split("/")[-1]
790+
# validate attempts
791+
for i in range(num_retryable):
792+
attempt = handler.completed_attempts[i]
793+
assert isinstance(attempt, CompletedAttemptMetric)
794+
assert attempt.end_status.name == "ABORTED"
795+
assert attempt.gfe_latency_ns is None
796+
final_attempt = handler.completed_attempts[num_retryable]
797+
assert isinstance(final_attempt, CompletedAttemptMetric)
798+
assert final_attempt.end_status.name == "OK"
799+
assert final_attempt.gfe_latency_ns > 0 and final_attempt.gfe_latency_ns < operation.duration_ns
687800

688801

689802
@CrossSync.pytest
690803
async def test_sample_row_keys_failure_timeout(
691-
self, table, temp_rows, handler
804+
self, table, handler
692805
):
693806
"""
694807
Test failure in gapic layer by passing very low timeout
695808
696809
No grpc headers expected
697810
"""
698-
pass
699-
700-
@CrossSync.pytest
701-
async def test_sample_row_keys_failure_unauthorized(
702-
self, handler, authorized_view, cluster_config
703-
):
704-
"""
705-
Test failure in backend by accessing an unauthorized family
706-
"""
707-
pass
811+
with pytest.raises(GoogleAPICallError):
812+
await table.sample_row_keys(operation_timeout=0.001)
813+
# validate counts
814+
assert len(handler.completed_operations) == 1
815+
assert len(handler.completed_attempts) == 1
816+
assert len(handler.cancelled_operations) == 0
817+
# validate operation
818+
operation = handler.completed_operations[0]
819+
assert isinstance(operation, CompletedOperationMetric)
820+
assert operation.final_status.name == "DEADLINE_EXCEEDED"
821+
assert operation.op_type.value == "SampleRowKeys"
822+
assert operation.is_streaming is False
823+
assert len(operation.completed_attempts) == 1
824+
assert operation.cluster_id == "unspecified"
825+
assert operation.zone == "global"
826+
# validate attempt
827+
attempt = handler.completed_attempts[0]
828+
assert isinstance(attempt, CompletedAttemptMetric)
829+
assert attempt.end_status.name == "DEADLINE_EXCEEDED"
830+
assert attempt.gfe_latency_ns is None
708831

709832
@CrossSync.pytest
710833
async def test_sample_row_keys_failure_mid_stream(
711-
self, table, temp_rows, handler, error_injector
834+
self, table, temp_rows, handler, error_injector, cluster_config
712835
):
713836
"""
714837
Test failure in grpc stream
715838
"""
716-
pass
839+
error_injector.fail_mid_stream = True
840+
error_injector.push(Aborted("retryable"))
841+
error_injector.push(PermissionDenied("terminal"))
842+
with pytest.raises(PermissionDenied):
843+
await table.sample_row_keys(retryable_errors=[Aborted])
844+
# validate counts
845+
assert len(handler.completed_operations) == 1
846+
assert len(handler.completed_attempts) == 2
847+
assert len(handler.cancelled_operations) == 0
848+
# validate operation
849+
operation = handler.completed_operations[0]
850+
assert operation.final_status.name == "PERMISSION_DENIED"
851+
assert operation.op_type.value == "SampleRowKeys"
852+
assert operation.is_streaming is False
853+
assert len(operation.completed_attempts) == 2
854+
assert operation.cluster_id == next(iter(cluster_config.keys()))
855+
assert (
856+
operation.zone
857+
== cluster_config[operation.cluster_id].location.split("/")[-1]
858+
)
859+
# validate retried attempt
860+
attempt = handler.completed_attempts[0]
861+
assert attempt.end_status.name == "ABORTED"
862+
# validate final attempt
863+
final_attempt = handler.completed_attempts[-1]
864+
assert final_attempt.end_status.name == "PERMISSION_DENIED"
717865

718866

719867
@CrossSync.pytest
@@ -969,4 +1117,4 @@ async def test_check_and_mutate_row_failure_unauthorized(
9691117
assert operation.zone == cluster_config[operation.cluster_id].location.split("/")[-1]
9701118
# validate attempt
9711119
attempt = handler.completed_attempts[0]
972-
assert attempt.gfe_latency_ns >= 0 and attempt.gfe_latency_ns < operation.duration_ns
1120+
assert attempt.gfe_latency_ns >= 0 and attempt.gfe_latency_ns < operation.duration_ns

0 commit comments

Comments
 (0)