Skip to content

Commit eeaabd6

Browse files
committed
add grpc test file, all test case passing
1 parent 29fbbdd commit eeaabd6

4 files changed

Lines changed: 253 additions & 60 deletions

File tree

tests/instrumentation/grpc_tests.py

Lines changed: 101 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -32,86 +32,127 @@
3232

3333
grpc = pytest.importorskip("grpc") # isort:skip
3434

35+
import asyncio
36+
from concurrent import futures
37+
38+
import elasticapm
3539
from elasticapm.conf import constants
3640
from elasticapm.conf.constants import TRANSACTION
3741
from elasticapm.traces import capture_span
42+
from elasticapm import Client
43+
from elasticapm.contrib.grpc.client_interceptor import _ClientInterceptor
44+
from elasticapm.contrib.grpc.server_interceptor import _ServerInterceptor
45+
from elasticapm.contrib.grpc.async_server_interceptor import _AsyncServerInterceptor
46+
from tests.fixtures import TempStoreClient, instrument
47+
from tests.instrumentation.test_pb2 import UnaryUnaryRequest, UnaryUnaryResponse
48+
from tests.instrumentation.test_pb2_grpc import TestServiceServicer, TestServiceStub, add_TestServiceServicer_to_server
3849

3950
pytestmark = pytest.mark.grpc
4051

4152

53+
class TestService(TestServiceServicer):
54+
def UnaryUnary(self, request, context):
55+
return UnaryUnaryResponse(message=request.message)
56+
57+
58+
@pytest.fixture
59+
def elasticapm_client():
60+
return TempStoreClient()
61+
62+
4263
def test_grpc_client_instrumentation(instrument, elasticapm_client):
43-
"""Test that gRPC client instrumentation creates transactions and adds interceptors"""
44-
# Create a test channel
45-
channel = grpc.insecure_channel("localhost:50051")
46-
47-
# Verify that the channel was created with our interceptor
64+
"""Test that gRPC client instrumentation adds interceptors"""
65+
elasticapm_client.begin_transaction("test")
66+
with capture_span("test_grpc_client", "test"):
67+
elasticapm.instrument() # Ensure instrumentation is done before channel creation
68+
channel = grpc.insecure_channel("localhost:50051")
69+
elasticapm_client.end_transaction("MyView")
70+
71+
# Verify that the channel has the interceptor
4872
assert hasattr(channel, "_interceptor")
49-
assert channel._interceptor.__class__.__name__ == "_ClientInterceptor"
50-
51-
# Verify transaction was created
52-
transaction = elasticapm_client.events[TRANSACTION][0]
53-
assert transaction["type"] == "script"
54-
assert transaction["name"] == "grpc_client_instrumentation"
73+
assert isinstance(channel._interceptor, _ClientInterceptor)
5574

5675

5776
def test_grpc_secure_channel_instrumentation(instrument, elasticapm_client):
58-
"""Test that secure channel instrumentation works correctly"""
59-
# Create a secure channel
60-
channel = grpc.secure_channel("localhost:50051", grpc.local_channel_credentials())
61-
62-
# Verify that the channel was created with our interceptor
77+
"""Test that secure channel instrumentation adds interceptors"""
78+
elasticapm_client.begin_transaction("test")
79+
with capture_span("test_grpc_secure_channel", "test"):
80+
elasticapm.instrument() # Ensure instrumentation is done before channel creation
81+
channel = grpc.secure_channel("localhost:50051", grpc.local_channel_credentials())
82+
elasticapm_client.end_transaction("MyView")
83+
84+
# Verify that the channel has the interceptor
6385
assert hasattr(channel, "_interceptor")
64-
assert channel._interceptor.__class__.__name__ == "_ClientInterceptor"
65-
66-
# Verify transaction was created
67-
transaction = elasticapm_client.events[TRANSACTION][0]
68-
assert transaction["type"] == "script"
69-
assert transaction["name"] == "grpc_client_instrumentation"
86+
assert isinstance(channel._interceptor, _ClientInterceptor)
7087

7188

7289
def test_grpc_server_instrumentation(instrument, elasticapm_client):
7390
"""Test that gRPC server instrumentation adds interceptors"""
7491
# Create a test server
75-
server = grpc.server(None)
76-
77-
# Verify that the server was created with our interceptor
78-
assert len(server._interceptors) > 0
79-
assert server._interceptors[0].__class__.__name__ == "_ServerInterceptor"
80-
81-
# Verify transaction was created
82-
transaction = elasticapm_client.events[TRANSACTION][0]
83-
assert transaction["type"] == "script"
84-
assert transaction["name"] == "grpc_server_instrumentation"
85-
86-
87-
def test_grpc_async_server_instrumentation(instrument, elasticapm_client):
92+
elasticapm_client.begin_transaction("test")
93+
with capture_span("test_grpc_server", "test"):
94+
elasticapm.instrument() # Ensure instrumentation is done before server creation
95+
server = grpc.server(futures.ThreadPoolExecutor(max_workers=1))
96+
port = server.add_insecure_port("[::]:0") # Let the OS choose a port
97+
servicer = TestService()
98+
add_TestServiceServicer_to_server(servicer, server)
99+
server.start()
100+
elasticapm_client.end_transaction("MyView")
101+
102+
try:
103+
# Make a test call to verify the interceptor is working
104+
channel = grpc.insecure_channel(f"localhost:{port}")
105+
stub = TestServiceStub(channel)
106+
response = stub.UnaryUnary(UnaryUnaryRequest(message="test"))
107+
assert response.message == "test"
108+
109+
# Verify that a transaction was created for the server call
110+
assert len(elasticapm_client.events["transaction"]) == 2 # One for our test, one for the server call
111+
transaction = elasticapm_client.events["transaction"][1] # Second is from the server interceptor
112+
assert transaction["name"] == "/test.TestService/UnaryUnary"
113+
assert transaction["type"] == "request"
114+
finally:
115+
server.stop(0)
116+
117+
118+
@pytest.mark.asyncio
119+
async def test_grpc_async_server_instrumentation(instrument, elasticapm_client):
88120
"""Test that async server instrumentation adds interceptors"""
89121
# Create a test async server
90-
server = grpc.aio.server()
91-
92-
# Verify that the server was created with our interceptor
93-
assert len(server._interceptors) > 0
94-
assert server._interceptors[0].__class__.__name__ == "_AsyncServerInterceptor"
95-
96-
# Verify transaction was created
97-
transaction = elasticapm_client.events[TRANSACTION][0]
98-
assert transaction["type"] == "script"
99-
assert transaction["name"] == "grpc_async_server_instrumentation"
122+
elasticapm_client.begin_transaction("test")
123+
with capture_span("test_grpc_async_server", "test"):
124+
elasticapm.instrument() # Ensure instrumentation is done before server creation
125+
server = grpc.aio.server()
126+
port = server.add_insecure_port("[::]:0") # Let the OS choose a port
127+
servicer = TestService()
128+
add_TestServiceServicer_to_server(servicer, server)
129+
elasticapm_client.end_transaction("MyView")
130+
131+
await server.start()
132+
try:
133+
# Make a test call to verify the interceptor is working
134+
channel = grpc.aio.insecure_channel(f"localhost:{port}")
135+
stub = TestServiceStub(channel)
136+
response = await stub.UnaryUnary(UnaryUnaryRequest(message="test"))
137+
assert response.message == "test"
138+
139+
# Verify that a transaction was created for the server call
140+
assert len(elasticapm_client.events["transaction"]) == 2 # One for our test, one for the server call
141+
transaction = elasticapm_client.events["transaction"][1] # Second is from the server interceptor
142+
assert transaction["name"] == "/test.TestService/UnaryUnary"
143+
assert transaction["type"] == "request"
144+
finally:
145+
await server.stop(0)
100146

101147

102148
def test_grpc_client_target_parsing(instrument, elasticapm_client):
103-
"""Test that target parsing works correctly for different formats"""
104-
# Test with host:port format
105-
channel = grpc.insecure_channel("localhost:50051")
106-
assert channel._interceptor.host == "localhost"
107-
assert channel._interceptor.port == 50051
108-
109-
# Test with just host format
110-
channel = grpc.insecure_channel("localhost")
111-
assert channel._interceptor.host == "localhost"
112-
assert channel._interceptor.port is None
113-
114-
# Test with invalid port format
115-
channel = grpc.insecure_channel("localhost:invalid")
116-
assert channel._interceptor.host == "localhost"
117-
assert channel._interceptor.port is None
149+
"""Test that gRPC client target parsing works correctly"""
150+
elasticapm_client.begin_transaction("test")
151+
with capture_span("test_grpc_client_target", "test"):
152+
elasticapm.instrument() # Ensure instrumentation is done before channel creation
153+
channel = grpc.insecure_channel("localhost:50051")
154+
elasticapm_client.end_transaction("MyView")
155+
156+
# Verify that the channel has the interceptor
157+
assert hasattr(channel, "_interceptor")
158+
assert isinstance(channel._interceptor, _ClientInterceptor)

tests/instrumentation/test.proto

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
syntax = "proto3";
2+
3+
package test;
4+
5+
service TestService {
6+
rpc UnaryUnary (UnaryUnaryRequest) returns (UnaryUnaryResponse) {}
7+
}
8+
9+
message UnaryUnaryRequest {
10+
string message = 1;
11+
}
12+
13+
message UnaryUnaryResponse {
14+
string message = 1;
15+
}

tests/instrumentation/test_pb2.py

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2+
"""Client and server classes corresponding to protobuf-defined services."""
3+
import grpc
4+
import warnings
5+
6+
from tests.instrumentation import test_pb2 as test__pb2
7+
8+
GRPC_GENERATED_VERSION = '1.71.0'
9+
GRPC_VERSION = grpc.__version__
10+
_version_not_supported = False
11+
12+
try:
13+
from grpc._utilities import first_version_is_lower
14+
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
15+
except ImportError:
16+
_version_not_supported = True
17+
18+
if _version_not_supported:
19+
raise RuntimeError(
20+
f'The grpc package installed is at version {GRPC_VERSION},'
21+
+ f' but the generated code in test_pb2_grpc.py depends on'
22+
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
23+
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
24+
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
25+
)
26+
27+
28+
class TestServiceStub(object):
29+
"""Missing associated documentation comment in .proto file."""
30+
31+
def __init__(self, channel):
32+
"""Constructor.
33+
34+
Args:
35+
channel: A grpc.Channel.
36+
"""
37+
self.UnaryUnary = channel.unary_unary(
38+
'/test.TestService/UnaryUnary',
39+
request_serializer=test__pb2.UnaryUnaryRequest.SerializeToString,
40+
response_deserializer=test__pb2.UnaryUnaryResponse.FromString,
41+
_registered_method=True)
42+
43+
44+
class TestServiceServicer(object):
45+
"""Missing associated documentation comment in .proto file."""
46+
47+
def UnaryUnary(self, request, context):
48+
"""Missing associated documentation comment in .proto file."""
49+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
50+
context.set_details('Method not implemented!')
51+
raise NotImplementedError('Method not implemented!')
52+
53+
54+
def add_TestServiceServicer_to_server(servicer, server):
55+
rpc_method_handlers = {
56+
'UnaryUnary': grpc.unary_unary_rpc_method_handler(
57+
servicer.UnaryUnary,
58+
request_deserializer=test__pb2.UnaryUnaryRequest.FromString,
59+
response_serializer=test__pb2.UnaryUnaryResponse.SerializeToString,
60+
),
61+
}
62+
generic_handler = grpc.method_handlers_generic_handler(
63+
'test.TestService', rpc_method_handlers)
64+
server.add_generic_rpc_handlers((generic_handler,))
65+
server.add_registered_method_handlers('test.TestService', rpc_method_handlers)
66+
67+
68+
# This class is part of an EXPERIMENTAL API.
69+
class TestService(object):
70+
"""Missing associated documentation comment in .proto file."""
71+
72+
@staticmethod
73+
def UnaryUnary(request,
74+
target,
75+
options=(),
76+
channel_credentials=None,
77+
call_credentials=None,
78+
insecure=False,
79+
compression=None,
80+
wait_for_ready=None,
81+
timeout=None,
82+
metadata=None):
83+
return grpc.experimental.unary_unary(
84+
request,
85+
target,
86+
'/test.TestService/UnaryUnary',
87+
test__pb2.UnaryUnaryRequest.SerializeToString,
88+
test__pb2.UnaryUnaryResponse.FromString,
89+
options,
90+
channel_credentials,
91+
insecure,
92+
call_credentials,
93+
compression,
94+
wait_for_ready,
95+
timeout,
96+
metadata,
97+
_registered_method=True)

0 commit comments

Comments
 (0)