Skip to content

Commit 4b07aac

Browse files
authored
tests: Add logging acceptance tests (#373)
* service: Rename SessionManagementClient * tests: Add logging acceptance tests * tests: Fix name conflict between the `request` fixture and a gRPC request variable
1 parent 0cebe80 commit 4b07aac

4 files changed

Lines changed: 191 additions & 4 deletions

File tree

ni_measurementlink_service/session_management.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,17 @@
55
import warnings
66
from functools import cached_property
77
from types import TracebackType
8-
from typing import Any, Iterable, List, Literal, NamedTuple, Optional, Sequence, Type, TypeVar
8+
from typing import (
9+
Any,
10+
Iterable,
11+
List,
12+
Literal,
13+
NamedTuple,
14+
Optional,
15+
Sequence,
16+
Type,
17+
TypeVar,
18+
)
919

1020
import grpc
1121
from deprecation import DeprecatedWarning
@@ -235,7 +245,7 @@ def __getattr__(name: str) -> Any:
235245
raise AttributeError(f"module {__name__} has no attribute {name}")
236246

237247

238-
class Client(object):
248+
class SessionManagementClient(object):
239249
"""Class that manages driver sessions."""
240250

241251
def __init__(self, *, grpc_channel: grpc.Channel) -> None:
@@ -505,3 +515,7 @@ def reserve_all_registered_sessions(
505515
self._client.ReserveAllRegisteredSessions(request)
506516
)
507517
return MultiSessionReservation(session_manager=self, session_info=response.sessions)
518+
519+
520+
Client = SessionManagementClient
521+
"""Alias for compatibility with code that uses session_management.Client."""

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ build-backend = "poetry.core.masonry.api"
7272
addopts = "--doctest-modules --strict-markers"
7373
filterwarnings = ["always::ImportWarning", "always::ResourceWarning"]
7474
testpaths = ["tests"]
75+
markers = [
76+
"service_class: specifies which test service to use.",
77+
]
7578

7679
[tool.mypy]
7780
warn_unused_configs = true

tests/acceptance/test_logging.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import logging
2+
import re
3+
from typing import Generator
4+
5+
import pytest
6+
from pytest import FixtureRequest, LogCaptureFixture
7+
8+
from ni_measurementlink_service import session_management
9+
from ni_measurementlink_service._internal.discovery_client import DiscoveryClient
10+
from ni_measurementlink_service._internal.stubs.ni.measurementlink.measurement.v2 import (
11+
measurement_service_pb2 as v2_measurement_service_pb2,
12+
measurement_service_pb2_grpc as v2_measurement_service_pb2_grpc,
13+
)
14+
from ni_measurementlink_service.measurement.service import MeasurementService
15+
from ni_measurementlink_service.session_management import SessionManagementClient
16+
from tests.acceptance.test_streaming_data_measurement import (
17+
_get_configuration_parameters as get_streaming_data_configuration_parameters,
18+
)
19+
from tests.utilities import loopback_measurement, streaming_data_measurement
20+
from tests.utilities.discovery_service_process import DiscoveryServiceProcess
21+
22+
23+
def test___discovery_client___call___client_call_logged(
24+
caplog: LogCaptureFixture, discovery_client: DiscoveryClient
25+
) -> None:
26+
with caplog.at_level(logging.DEBUG):
27+
_ = discovery_client.resolve_service(
28+
session_management.GRPC_SERVICE_INTERFACE_NAME, session_management.GRPC_SERVICE_CLASS
29+
)
30+
31+
method_name = "/ni.measurementlink.discovery.v1.DiscoveryService/ResolveService"
32+
debug_messages = [r.message for r in caplog.records if r.levelno == logging.DEBUG]
33+
assert f"gRPC client call starting: {method_name}" in debug_messages
34+
assert f"gRPC client call complete: {method_name}" in debug_messages
35+
36+
37+
def test___session_management_client___call___client_call_logged(
38+
caplog: LogCaptureFixture, request: FixtureRequest
39+
) -> None:
40+
with caplog.at_level(logging.DEBUG):
41+
# HACK: set log level before constructing client
42+
session_management_client: SessionManagementClient = request.getfixturevalue(
43+
"session_management_client"
44+
)
45+
46+
session_management_client.unregister_sessions(session_info=[])
47+
48+
method_name = (
49+
"/ni.measurementlink.sessionmanagement.v1.SessionManagementService/UnregisterSessions"
50+
)
51+
debug_messages = [r.message for r in caplog.records if r.levelno == logging.DEBUG]
52+
assert f"gRPC client call starting: {method_name}" in debug_messages
53+
assert f"gRPC client call complete: {method_name}" in debug_messages
54+
55+
56+
@pytest.mark.service_class("ni.tests.LoopbackMeasurement_Python")
57+
def test___loopback_measurement___get_metadata___server_call_logged(
58+
caplog: LogCaptureFixture, request: FixtureRequest
59+
) -> None:
60+
with caplog.at_level(logging.DEBUG):
61+
# HACK: set log level before constructing server
62+
stub_v2: v2_measurement_service_pb2_grpc.MeasurementServiceStub = request.getfixturevalue(
63+
"stub_v2"
64+
)
65+
66+
_ = stub_v2.GetMetadata(v2_measurement_service_pb2.GetMetadataRequest())
67+
68+
method_name = "/ni.measurementlink.measurement.v2.MeasurementService/GetMetadata"
69+
debug_messages = [r.message for r in caplog.records if r.levelno == logging.DEBUG]
70+
assert f"gRPC server call starting: {method_name}" in debug_messages
71+
assert f"gRPC server call complete: {method_name}" in debug_messages
72+
info_messages = [r.message for r in caplog.records if r.levelno == logging.INFO]
73+
info_regex = re.compile(rf"gRPC server call {method_name} responded OK in [\d.]+ ms")
74+
assert len(list(filter(info_regex.match, info_messages))) == 1
75+
76+
77+
@pytest.mark.service_class("ni.tests.StreamingDataMeasurement_Python")
78+
def test___streaming_data_measurement___measure___server_call_logged(
79+
caplog: LogCaptureFixture, request: FixtureRequest
80+
) -> None:
81+
with caplog.at_level(logging.DEBUG):
82+
# HACK: set log level before constructing server
83+
stub_v2: v2_measurement_service_pb2_grpc.MeasurementServiceStub = request.getfixturevalue(
84+
"stub_v2"
85+
)
86+
num_responses = 10
87+
88+
measure_request = v2_measurement_service_pb2.MeasureRequest(
89+
configuration_parameters=get_streaming_data_configuration_parameters(
90+
num_responses=num_responses
91+
)
92+
)
93+
response_iterator = stub_v2.Measure(measure_request)
94+
for response in response_iterator:
95+
pass
96+
97+
method_name = "/ni.measurementlink.measurement.v2.MeasurementService/Measure"
98+
debug_messages = [r.message for r in caplog.records if r.levelno == logging.DEBUG]
99+
assert f"gRPC server call starting: {method_name}" in debug_messages
100+
assert f"gRPC server call complete: {method_name}" in debug_messages
101+
debug_response_regex = re.compile(rf"gRPC server call streaming response: {method_name}")
102+
assert len(list(filter(debug_response_regex.match, debug_messages))) == num_responses
103+
info_messages = [r.message for r in caplog.records if r.levelno == logging.INFO]
104+
info_regex = re.compile(rf"gRPC server call {method_name} responded OK in [\d.]+ ms")
105+
assert len(list(filter(info_regex.match, info_messages))) == 1
106+
107+
108+
@pytest.fixture
109+
def measurement_service(request: FixtureRequest) -> MeasurementService:
110+
service_class_marker = request.node.get_closest_marker("service_class")
111+
service_class = service_class_marker.args[0]
112+
if service_class == "ni.tests.LoopbackMeasurement_Python":
113+
return request.getfixturevalue("loopback_measurement_service")
114+
elif service_class == "ni.tests.StreamingDataMeasurement_Python":
115+
return request.getfixturevalue("streaming_data_measurement_service")
116+
else:
117+
raise ValueError(f"Unsupported service class: {service_class}")
118+
119+
120+
@pytest.fixture
121+
def loopback_measurement_service(
122+
discovery_service_process: DiscoveryServiceProcess,
123+
) -> Generator[MeasurementService, None, None]:
124+
"""Test fixture that creates a loopback measurement."""
125+
with loopback_measurement.measurement_service.host_service() as service:
126+
yield service
127+
128+
129+
@pytest.fixture
130+
def streaming_data_measurement_service(
131+
discovery_service_process: DiscoveryServiceProcess,
132+
) -> Generator[MeasurementService, None, None]:
133+
"""Test fixture that creates a loopback measurement."""
134+
with streaming_data_measurement.measurement_service.host_service() as service:
135+
yield service

tests/conftest.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,22 @@
66
import grpc
77
import pytest
88

9-
from ni_measurementlink_service._internal.discovery_client import _get_registration_json_file_path
9+
from ni_measurementlink_service import session_management
10+
from ni_measurementlink_service._internal.discovery_client import (
11+
DiscoveryClient,
12+
_get_registration_json_file_path,
13+
)
1014
from ni_measurementlink_service._internal.stubs.ni.measurementlink.measurement.v1 import (
1115
measurement_service_pb2_grpc as v1_measurement_service_pb2_grpc,
1216
)
1317
from ni_measurementlink_service._internal.stubs.ni.measurementlink.measurement.v2 import (
1418
measurement_service_pb2_grpc as v2_measurement_service_pb2_grpc,
1519
)
16-
from ni_measurementlink_service.measurement.service import MeasurementService
20+
from ni_measurementlink_service.measurement.service import (
21+
GrpcChannelPool,
22+
MeasurementService,
23+
)
24+
from ni_measurementlink_service.session_management import SessionManagementClient
1725
from tests.utilities.discovery_service_process import DiscoveryServiceProcess
1826

1927

@@ -63,3 +71,30 @@ def discovery_service_process() -> Generator[DiscoveryServiceProcess, None, None
6371
else:
6472
with DiscoveryServiceProcess() as proc:
6573
yield proc
74+
75+
76+
@pytest.fixture(scope="session")
77+
def grpc_channel_pool() -> Generator[GrpcChannelPool, None, None]:
78+
"""Test fixture that creates a gRPC channel pool."""
79+
with GrpcChannelPool() as grpc_channel_pool:
80+
yield grpc_channel_pool
81+
82+
83+
@pytest.fixture
84+
def discovery_client(discovery_service_process: DiscoveryServiceProcess) -> DiscoveryClient:
85+
"""Test fixture that creates a discovery client."""
86+
# TODO: DiscoveryClient should accept a GrpcChannelPool
87+
return DiscoveryClient()
88+
89+
90+
@pytest.fixture
91+
def session_management_client(
92+
discovery_client: DiscoveryClient, grpc_channel_pool: GrpcChannelPool
93+
) -> SessionManagementClient:
94+
"""Test fixture that creates a session management client."""
95+
# TODO: SessionManagementClient should accept a GrpcChannelPool
96+
location = discovery_client.resolve_service(
97+
session_management.GRPC_SERVICE_INTERFACE_NAME, session_management.GRPC_SERVICE_CLASS
98+
)
99+
grpc_channel = grpc_channel_pool.get_channel(location.insecure_address)
100+
return SessionManagementClient(grpc_channel=grpc_channel)

0 commit comments

Comments
 (0)