Skip to content

Commit 4b39bc5

Browse files
committed
added tests
1 parent 428d75a commit 4b39bc5

File tree

5 files changed

+261
-6
lines changed

5 files changed

+261
-6
lines changed

google/cloud/bigtable/data/_async/client.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,7 @@ def __init__(
243243
stacklevel=2,
244244
)
245245

246-
@CrossSync.convert(
247-
replace_symbols={"AsyncSwappableChannel": "SwappableChannel"}
248-
)
246+
@CrossSync.convert(replace_symbols={"AsyncSwappableChannel": "SwappableChannel"})
249247
def _build_grpc_channel(self, *args, **kwargs) -> AsyncSwappableChannel:
250248
if self._emulator_host is not None:
251249
# emulators use insecure channel
@@ -356,9 +354,7 @@ def _invalidate_channel_stubs(self):
356354
self.transport._stubs = {}
357355
self.transport._prep_wrapped_messages(self.client_info)
358356

359-
@CrossSync.convert(
360-
replace_symbols={"AsyncSwappableChannel": "SwappableChannel"}
361-
)
357+
@CrossSync.convert(replace_symbols={"AsyncSwappableChannel": "SwappableChannel"})
362358
async def _manage_channel(
363359
self,
364360
refresh_interval_min: float = 60 * 35,

tests/system/data/test_system_async.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@
2929

3030
from . import TEST_FAMILY, TEST_FAMILY_2
3131

32+
if CrossSync.is_async:
33+
from google.cloud.bigtable_v2.services.bigtable.transports.grpc_asyncio import (
34+
_LoggingClientAIOInterceptor as GapicInterceptor,
35+
)
36+
else:
37+
from google.cloud.bigtable_v2.services.bigtable.transports.grpc import (
38+
_LoggingClientInterceptor as GapicInterceptor,
39+
)
3240

3341
__CROSS_SYNC_OUTPUT__ = "tests.system.data.test_system_autogen"
3442

@@ -260,6 +268,16 @@ async def test_channel_refresh(self, table_id, instance_id, temp_rows):
260268
assert len(rows_after_refresh) == 2
261269
assert client.transport.grpc_channel is channel_wrapper
262270
assert client.transport.grpc_channel._channel is not first_channel
271+
# ensure gapic's logging interceptor is still active
272+
if CrossSync.is_async:
273+
interceptors = (
274+
client.transport.grpc_channel._channel._unary_unary_interceptors
275+
)
276+
assert GapicInterceptor in [type(i) for i in interceptors]
277+
else:
278+
assert isinstance(
279+
client.transport._logged_channel._interceptor, GapicInterceptor
280+
)
263281
finally:
264282
await client.close()
265283

tests/system/data/test_system_autogen.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
from google.type import date_pb2
2828
from google.cloud.bigtable.data._cross_sync import CrossSync
2929
from . import TEST_FAMILY, TEST_FAMILY_2
30+
from google.cloud.bigtable_v2.services.bigtable.transports.grpc import (
31+
_LoggingClientInterceptor as GapicInterceptor,
32+
)
3033

3134
TARGETS = ["table"]
3235
if not os.environ.get(BIGTABLE_EMULATOR):
@@ -209,6 +212,9 @@ def test_channel_refresh(self, table_id, instance_id, temp_rows):
209212
assert len(rows_after_refresh) == 2
210213
assert client.transport.grpc_channel is channel_wrapper
211214
assert client.transport.grpc_channel._channel is not first_channel
215+
assert isinstance(
216+
client.transport._logged_channel._interceptor, GapicInterceptor
217+
)
212218
finally:
213219
client.close()
214220

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# try/except added for compatibility with python < 3.8
16+
try:
17+
from unittest import mock
18+
except ImportError: # pragma: NO COVER
19+
import mock # type: ignore
20+
21+
import pytest
22+
from grpc import ChannelConnectivity
23+
24+
from google.cloud.bigtable.data._cross_sync import CrossSync
25+
26+
if CrossSync.is_async:
27+
from google.cloud.bigtable.data._async._swappable_channel import (
28+
AsyncSwappableChannel as TargetType,
29+
)
30+
else:
31+
from google.cloud.bigtable.data._sync_autogen._swappable_channel import (
32+
SwappableChannel as TargetType,
33+
)
34+
35+
36+
__CROSS_SYNC_OUTPUT__ = "tests.unit.data._sync_autogen.test__swappable_channel"
37+
38+
39+
@CrossSync.convert_class(sync_name="TestSwappableChannel")
40+
class TestAsyncSwappableChannel:
41+
@staticmethod
42+
@CrossSync.convert
43+
def _get_target_class():
44+
return TargetType
45+
46+
def _make_one(self, *args, **kwargs):
47+
return self._get_target_class()(*args, **kwargs)
48+
49+
def test_ctor(self):
50+
channel_fn = mock.Mock()
51+
instance = self._make_one(channel_fn)
52+
assert instance._channel_fn == channel_fn
53+
channel_fn.assert_called_once_with()
54+
assert instance._channel == channel_fn.return_value
55+
56+
def test_swap_channel(self):
57+
channel_fn = mock.Mock()
58+
instance = self._make_one(channel_fn)
59+
old_channel = instance._channel
60+
new_channel = object()
61+
result = instance.swap_channel(new_channel)
62+
assert result == old_channel
63+
assert instance._channel == new_channel
64+
65+
def test_create_channel(self):
66+
channel_fn = mock.Mock()
67+
instance = self._make_one(channel_fn)
68+
# reset mock from ctor call
69+
channel_fn.reset_mock()
70+
new_channel = instance.create_channel()
71+
channel_fn.assert_called_once_with()
72+
assert new_channel == channel_fn.return_value
73+
74+
@CrossSync.drop
75+
def test_create_channel_async_interceptors_copied(self):
76+
channel_fn = mock.Mock()
77+
instance = self._make_one(channel_fn)
78+
# reset mock from ctor call
79+
channel_fn.reset_mock()
80+
# mock out interceptors on original channel
81+
instance._channel._unary_unary_interceptors = ["unary_unary"]
82+
instance._channel._unary_stream_interceptors = ["unary_stream"]
83+
instance._channel._stream_unary_interceptors = ["stream_unary"]
84+
instance._channel._stream_stream_interceptors = ["stream_stream"]
85+
86+
new_channel = instance.create_channel()
87+
channel_fn.assert_called_once_with()
88+
assert new_channel == channel_fn.return_value
89+
assert new_channel._unary_unary_interceptors == ["unary_unary"]
90+
assert new_channel._unary_stream_interceptors == ["unary_stream"]
91+
assert new_channel._stream_unary_interceptors == ["stream_unary"]
92+
assert new_channel._stream_stream_interceptors == ["stream_stream"]
93+
94+
@pytest.mark.parametrize(
95+
"method_name,args,kwargs",
96+
[
97+
("unary_unary", (1,), {"kw": 2}),
98+
("unary_stream", (3,), {"kw": 4}),
99+
("stream_unary", (5,), {"kw": 6}),
100+
("stream_stream", (7,), {"kw": 8}),
101+
("get_state", (), {"try_to_connect": True}),
102+
],
103+
)
104+
def test_forwarded_methods(self, method_name, args, kwargs):
105+
channel_fn = mock.Mock()
106+
instance = self._make_one(channel_fn)
107+
method = getattr(instance, method_name)
108+
result = method(*args, **kwargs)
109+
mock_method = getattr(channel_fn.return_value, method_name)
110+
mock_method.assert_called_once_with(*args, **kwargs)
111+
assert result == mock_method.return_value
112+
113+
@pytest.mark.parametrize(
114+
"method_name,args,kwargs",
115+
[
116+
("channel_ready", (), {}),
117+
("wait_for_state_change", (ChannelConnectivity.READY,), {}),
118+
],
119+
)
120+
@CrossSync.pytest
121+
async def test_forwarded_async_methods(self, method_name, args, kwargs):
122+
async def dummy_coro(*a, **k):
123+
return mock.sentinel.result
124+
125+
channel = mock.Mock()
126+
mock_method = getattr(channel, method_name)
127+
mock_method.side_effect = dummy_coro
128+
129+
channel_fn = mock.Mock(return_value=channel)
130+
instance = self._make_one(channel_fn)
131+
method = getattr(instance, method_name)
132+
result = await method(*args, **kwargs)
133+
134+
mock_method.assert_called_once_with(*args, **kwargs)
135+
assert result == mock.sentinel.result
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# try/except added for compatibility with python < 3.8
16+
17+
# This file is automatically generated by CrossSync. Do not edit manually.
18+
19+
try:
20+
from unittest import mock
21+
except ImportError:
22+
import mock
23+
import pytest
24+
from grpc import ChannelConnectivity
25+
from google.cloud.bigtable.data._sync_autogen._swappable_channel import (
26+
SwappableChannel as TargetType,
27+
)
28+
29+
30+
class TestSwappableChannel:
31+
@staticmethod
32+
def _get_target_class():
33+
return TargetType
34+
35+
def _make_one(self, *args, **kwargs):
36+
return self._get_target_class()(*args, **kwargs)
37+
38+
def test_ctor(self):
39+
channel_fn = mock.Mock()
40+
instance = self._make_one(channel_fn)
41+
assert instance._channel_fn == channel_fn
42+
channel_fn.assert_called_once_with()
43+
assert instance._channel == channel_fn.return_value
44+
45+
def test_swap_channel(self):
46+
channel_fn = mock.Mock()
47+
instance = self._make_one(channel_fn)
48+
old_channel = instance._channel
49+
new_channel = object()
50+
result = instance.swap_channel(new_channel)
51+
assert result == old_channel
52+
assert instance._channel == new_channel
53+
54+
def test_create_channel(self):
55+
channel_fn = mock.Mock()
56+
instance = self._make_one(channel_fn)
57+
channel_fn.reset_mock()
58+
new_channel = instance.create_channel()
59+
channel_fn.assert_called_once_with()
60+
assert new_channel == channel_fn.return_value
61+
62+
@pytest.mark.parametrize(
63+
"method_name,args,kwargs",
64+
[
65+
("unary_unary", (1,), {"kw": 2}),
66+
("unary_stream", (3,), {"kw": 4}),
67+
("stream_unary", (5,), {"kw": 6}),
68+
("stream_stream", (7,), {"kw": 8}),
69+
("get_state", (), {"try_to_connect": True}),
70+
],
71+
)
72+
def test_forwarded_methods(self, method_name, args, kwargs):
73+
channel_fn = mock.Mock()
74+
instance = self._make_one(channel_fn)
75+
method = getattr(instance, method_name)
76+
result = method(*args, **kwargs)
77+
mock_method = getattr(channel_fn.return_value, method_name)
78+
mock_method.assert_called_once_with(*args, **kwargs)
79+
assert result == mock_method.return_value
80+
81+
@pytest.mark.parametrize(
82+
"method_name,args,kwargs",
83+
[
84+
("channel_ready", (), {}),
85+
("wait_for_state_change", (ChannelConnectivity.READY,), {}),
86+
],
87+
)
88+
def test_forwarded_async_methods(self, method_name, args, kwargs):
89+
def dummy_coro(*a, **k):
90+
return mock.sentinel.result
91+
92+
channel = mock.Mock()
93+
mock_method = getattr(channel, method_name)
94+
mock_method.side_effect = dummy_coro
95+
channel_fn = mock.Mock(return_value=channel)
96+
instance = self._make_one(channel_fn)
97+
method = getattr(instance, method_name)
98+
result = method(*args, **kwargs)
99+
mock_method.assert_called_once_with(*args, **kwargs)
100+
assert result == mock.sentinel.result

0 commit comments

Comments
 (0)