Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test case for measurements with no output configuration in client generation acceptance test #900

Merged
merged 5 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,15 @@
"""Generated client API for the ${display_name | repr} measurement plug-in."""

import logging
import pathlib
import threading
% if len(enum_by_class_name):
from enum import Enum
% endif
% for module in built_in_import_modules:
${module}
% endfor
from typing import Any, Generator, Iterable, List, NamedTuple, Optional
from pathlib import Path
from typing import Any, Generator${", Iterable" if output_metadata else ""}, List${", NamedTuple" if output_metadata else ""}, Optional
Jotheeswaran-Nandagopal marked this conversation as resolved.
Show resolved Hide resolved

import grpc
from google.protobuf import any_pb2
from google.protobuf import descriptor_pool
from google.protobuf import any_pb2, descriptor_pool
from ni_measurement_plugin_sdk_service._internal.stubs.ni.measurementlink.measurement.v2 import (
measurement_service_pb2 as v2_measurement_service_pb2,
measurement_service_pb2_grpc as v2_measurement_service_pb2_grpc,
Expand All @@ -27,7 +23,9 @@ from ni_measurement_plugin_sdk_service.discovery import DiscoveryClient
from ni_measurement_plugin_sdk_service.grpc.channelpool import GrpcChannelPool
from ni_measurement_plugin_sdk_service.measurement.client_support import (
create_file_descriptor,
% if output_metadata:
deserialize_parameters,
% endif
ParameterMetadata,
serialize_parameters,
)
Expand Down Expand Up @@ -267,7 +265,7 @@ class ${class_name}:
else:
return False

def register_pin_map(self, pin_map_path: pathlib.Path) -> None:
def register_pin_map(self, pin_map_path: Path) -> None:
"""Registers the pin map with the pin map service.

Args:
Expand Down
60 changes: 52 additions & 8 deletions packages/generator/tests/acceptance/test_client_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,18 @@

from tests.conftest import CliRunnerFunction
from tests.utilities.discovery_service_process import DiscoveryServiceProcess
from tests.utilities.measurements import non_streaming_data_measurement, streaming_data_measurement
from tests.utilities.measurements import (
non_streaming_data_measurement,
streaming_data_measurement,
void_measurement,
)


def test___command_line_args___create_client___render_without_error(
def test___command_line_args_for_non_streaming_measurement___create_client___render_without_error(
Jotheeswaran-Nandagopal marked this conversation as resolved.
Show resolved Hide resolved
create_client: CliRunnerFunction,
test_assets_directory: pathlib.Path,
tmp_path_factory: pytest.TempPathFactory,
measurement_service: MeasurementService,
non_streaming_measurement_service: MeasurementService,
) -> None:
temp_directory = tmp_path_factory.mktemp("measurement_plugin_client_files")
module_name = "non_streaming_data_measurement_client"
Expand All @@ -43,7 +47,38 @@ def test___command_line_args___create_client___render_without_error(
)


def test___command_line_args___create_client_for_all_registered_measurements___renders_without_error(
def test___command_line_args_for_void_measurement___create_client___render_without_error(
create_client: CliRunnerFunction,
test_assets_directory: pathlib.Path,
tmp_path_factory: pytest.TempPathFactory,
void_measurement_service: MeasurementService,
) -> None:
temp_directory = tmp_path_factory.mktemp("measurement_plugin_client_files")
module_name = "void_measurement_client"
golden_path = test_assets_directory / "example_renders" / "measurement_plugin_client"
filename = f"{module_name}.py"

result = create_client(
[
"--measurement-service-class",
"ni.tests.VoidMeasurement_Python",
"--module-name",
module_name,
"--class-name",
"VoidMeasurementClient",
"--directory-out",
str(temp_directory),
]
)

assert result.exit_code == 0
_assert_equal(
golden_path / filename,
temp_directory / filename,
)


def test___command_line_args_for_all_registered_measurements___create_client___renders_without_error(
create_client: CliRunnerFunction,
tmp_path_factory: pytest.TempPathFactory,
multiple_measurement_service: MeasurementService,
Expand Down Expand Up @@ -76,7 +111,7 @@ def test___command_line_args_with_registered_measurements___create_client_using_
create_client: CliRunnerFunction,
test_assets_directory: pathlib.Path,
tmp_path_factory: pytest.TempPathFactory,
measurement_service: MeasurementService,
non_streaming_measurement_service: MeasurementService,
) -> None:
temp_directory = tmp_path_factory.mktemp("measurement_plugin_client_files")
golden_path = test_assets_directory / "example_renders" / "measurement_plugin_client"
Expand Down Expand Up @@ -109,7 +144,7 @@ def test___command_line_args_without_registering_any_measurement___create_client
def test___command_line_args___create_client___render_with_proper_line_ending(
create_client: CliRunnerFunction,
tmp_path_factory: pytest.TempPathFactory,
measurement_service: MeasurementService,
non_streaming_measurement_service: MeasurementService,
) -> None:
temp_directory = tmp_path_factory.mktemp("measurement_plugin_client_files")
module_name = "non_streaming_data_measurement_client"
Expand Down Expand Up @@ -154,14 +189,23 @@ def _assert_line_ending(file_path: pathlib.Path) -> None:


@pytest.fixture
def measurement_service(
def non_streaming_measurement_service(
discovery_service_process: DiscoveryServiceProcess,
) -> Generator[MeasurementService, None, None]:
"""Test fixture that creates and hosts a Measurement Plug-In Service."""
"""Test fixture that creates and hosts a non streaming Measurement Plug-In Service."""
with non_streaming_data_measurement.measurement_service.host_service() as service:
yield service


@pytest.fixture
def void_measurement_service(
discovery_service_process: DiscoveryServiceProcess,
) -> Generator[MeasurementService, None, None]:
"""Test fixture that creates and hosts a void Measurement Plug-In Service."""
with void_measurement.measurement_service.host_service() as service:
yield service


@pytest.fixture
def multiple_measurement_service(
discovery_service_process: DiscoveryServiceProcess,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
"""Generated client API for the 'Non-Streaming Data Measurement (Py)' measurement plug-in."""

import logging
import pathlib
import threading
from enum import Enum
from pathlib import Path
from typing import Any, Generator, Iterable, List, NamedTuple, Optional

import grpc
from google.protobuf import any_pb2
from google.protobuf import descriptor_pool
from google.protobuf import any_pb2, descriptor_pool
from ni_measurement_plugin_sdk_service._internal.stubs.ni.measurementlink.measurement.v2 import (
measurement_service_pb2 as v2_measurement_service_pb2,
measurement_service_pb2_grpc as v2_measurement_service_pb2_grpc,
Expand Down Expand Up @@ -621,7 +619,7 @@ def cancel(self) -> bool:
else:
return False

def register_pin_map(self, pin_map_path: pathlib.Path) -> None:
def register_pin_map(self, pin_map_path: Path) -> None:
"""Registers the pin map with the pin map service.

Args:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
"""Generated client API for the 'Void Measurement (Py)' measurement plug-in."""

import logging
import threading
from pathlib import Path
from typing import Any, Generator, List, Optional

import grpc
from google.protobuf import any_pb2, descriptor_pool
from ni_measurement_plugin_sdk_service._internal.stubs.ni.measurementlink.measurement.v2 import (
measurement_service_pb2 as v2_measurement_service_pb2,
measurement_service_pb2_grpc as v2_measurement_service_pb2_grpc,
)
from ni_measurement_plugin_sdk_service.discovery import DiscoveryClient
from ni_measurement_plugin_sdk_service.grpc.channelpool import GrpcChannelPool
from ni_measurement_plugin_sdk_service.measurement.client_support import (
create_file_descriptor,
ParameterMetadata,
serialize_parameters,
)
from ni_measurement_plugin_sdk_service.pin_map import PinMapClient
from ni_measurement_plugin_sdk_service.session_management import PinMapContext

_logger = logging.getLogger(__name__)

_V2_MEASUREMENT_SERVICE_INTERFACE = "ni.measurementlink.measurement.v2.MeasurementService"


class VoidMeasurementClient:
"""Client for the 'Void Measurement (Py)' measurement plug-in."""

def __init__(
self,
*,
discovery_client: Optional[DiscoveryClient] = None,
pin_map_client: Optional[PinMapClient] = None,
grpc_channel: Optional[grpc.Channel] = None,
grpc_channel_pool: Optional[GrpcChannelPool] = None,
):
"""Initialize the Measurement Plug-In Client.

Args:
discovery_client: An optional discovery client.

pin_map_client: An optional pin map client.

grpc_channel: An optional gRPC channel targeting a measurement service.

grpc_channel_pool: An optional gRPC channel pool.
"""
self._initialization_lock = threading.RLock()
self._service_class = "ni.tests.VoidMeasurement_Python"
self._grpc_channel_pool = grpc_channel_pool
self._discovery_client = discovery_client
self._pin_map_client = pin_map_client
self._stub: Optional[v2_measurement_service_pb2_grpc.MeasurementServiceStub] = None
self._measure_response: Optional[
Generator[v2_measurement_service_pb2.MeasureResponse, None, None]
] = None
self._configuration_metadata = {
1: ParameterMetadata(
display_name="Integer In",
type=5,
repeated=False,
default_value=10,
annotations={},
message_type="",
field_name="Integer_In",
enum_type=None,
)
}
self._output_metadata = {}
if grpc_channel is not None:
self._stub = v2_measurement_service_pb2_grpc.MeasurementServiceStub(grpc_channel)
self._create_file_descriptor()
self._pin_map_context: Optional[PinMapContext] = None

@property
def pin_map_context(self) -> PinMapContext:
"""Get the pin map context for the measurement."""
return self._pin_map_context

@pin_map_context.setter
def pin_map_context(self, val: PinMapContext) -> None:
if not isinstance(val, PinMapContext):
raise TypeError(
f"Invalid type {type(val)}: The given value must be an instance of PinMapContext."
)
self._pin_map_context = val

@property
def sites(self) -> List[int]:
"""The sites where the measurement must be executed."""
if self._pin_map_context is not None:
return self._pin_map_context.sites

@sites.setter
def sites(self, val: List[int]) -> None:
if self._pin_map_context is None:
raise AttributeError(
"Cannot set sites because the pin map context is None. Please provide a pin map context or register a pin map before setting sites."
)
self._pin_map_context = self._pin_map_context._replace(sites=val)

def _get_stub(self) -> v2_measurement_service_pb2_grpc.MeasurementServiceStub:
if self._stub is None:
with self._initialization_lock:
if self._stub is None:
service_location = self._get_discovery_client().resolve_service(
provided_interface=_V2_MEASUREMENT_SERVICE_INTERFACE,
service_class=self._service_class,
)
channel = self._get_grpc_channel_pool().get_channel(
service_location.insecure_address
)
self._stub = v2_measurement_service_pb2_grpc.MeasurementServiceStub(channel)
return self._stub

def _get_discovery_client(self) -> DiscoveryClient:
if self._discovery_client is None:
with self._initialization_lock:
if self._discovery_client is None:
self._discovery_client = DiscoveryClient(
grpc_channel_pool=self._get_grpc_channel_pool(),
)
return self._discovery_client

def _get_grpc_channel_pool(self) -> GrpcChannelPool:
if self._grpc_channel_pool is None:
with self._initialization_lock:
if self._grpc_channel_pool is None:
self._grpc_channel_pool = GrpcChannelPool()
return self._grpc_channel_pool

def _get_pin_map_client(self) -> PinMapClient:
if self._pin_map_client is None:
with self._initialization_lock:
if self._pin_map_client is None:
self._pin_map_client = PinMapClient(
discovery_client=self._get_discovery_client(),
grpc_channel_pool=self._get_grpc_channel_pool(),
)
return self._pin_map_client

def _create_file_descriptor(self) -> None:
create_file_descriptor(
input_metadata=list(self._configuration_metadata.values()),
output_metadata=list(self._output_metadata.values()),
service_name=self._service_class,
pool=descriptor_pool.Default(),
)

def _create_measure_request(
self, parameter_values: List[Any]
) -> v2_measurement_service_pb2.MeasureRequest:
serialized_configuration = any_pb2.Any(
Jotheeswaran-Nandagopal marked this conversation as resolved.
Show resolved Hide resolved
value=serialize_parameters(
parameter_metadata_dict=self._configuration_metadata,
parameter_values=parameter_values,
service_name=f"{self._service_class}.Configurations",
)
)
return v2_measurement_service_pb2.MeasureRequest(
configuration_parameters=serialized_configuration,
pin_map_context=self._pin_map_context._to_grpc() if self._pin_map_context else None,
)

def measure(self, integer_in: int = 10) -> None:
"""Perform a single measurement.

Returns:
Measurement outputs.
"""
stream_measure_response = self.stream_measure(integer_in)
for response in stream_measure_response:
pass

def stream_measure(self, integer_in: int = 10) -> Generator[None, None, None]:
"""Perform a streaming measurement.

Returns:
Stream of measurement outputs.
"""
parameter_values = [integer_in]
with self._initialization_lock:
if self._measure_response is not None:
raise RuntimeError(
"A measurement is currently in progress. To make concurrent measurement requests, please create a new client instance."
)
request = self._create_measure_request(parameter_values)
self._measure_response = self._get_stub().Measure(request)
try:
for response in self._measure_response:
yield
except grpc.RpcError as e:
if e.code() == grpc.StatusCode.CANCELLED:
_logger.debug("The measurement is canceled.")
raise
finally:
with self._initialization_lock:
self._measure_response = None

def cancel(self) -> bool:
"""Cancels the active measurement call."""
with self._initialization_lock:
if self._measure_response:
return self._measure_response.cancel()
else:
return False

def register_pin_map(self, pin_map_path: Path) -> None:
"""Registers the pin map with the pin map service.

Args:
pin_map_path: Absolute path of the pin map file.
"""
pin_map_id = self._get_pin_map_client().update_pin_map(pin_map_path)
if self._pin_map_context is None:
self._pin_map_context = PinMapContext(pin_map_id=pin_map_id, sites=[0])
else:
self._pin_map_context = self._pin_map_context._replace(pin_map_id=pin_map_id)
Loading