Skip to content

Commit 01dff89

Browse files
tests: Add integration and acceptance tests for NI-DCPower driver specific session management APIs (#490)
* tests: add nidcpower test measurement * tests: add proto file and stubs * tests: add integration tests for nidcpower driver-specific session APIs * tests: add acceptance tests for nidcpower driver-specific session APIs * style: restructure acceptance & integration tests files' directory * style: restructure assets and utilities
1 parent 71ffb3d commit 01dff89

20 files changed

+627
-106
lines changed

pyproject.toml

+2
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ module = [
124124
# https://github.com/briancurtin/deprecation/issues/56 - Add type information (PEP 561)
125125
"deprecation.*",
126126
"grpc.framework.foundation.*",
127+
# https://github.com/ni/hightime/issues/4 - Add type annotations
128+
"hightime.*",
127129
# https://github.com/microsoft/tracelogging/issues/57 - Python traceloggingdynamic package is missing py.typed marker file
128130
"traceloggingdynamic",
129131
# https://github.com/ni/nidaqmx-python/issues/209 - Support type annotations

tests/acceptance/conftest.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""Pytest configuration file for acceptance tests."""
2+
import pathlib
3+
4+
import pytest
5+
6+
7+
@pytest.fixture(scope="module")
8+
def pin_map_directory(test_assets_directory: pathlib.Path) -> pathlib.Path:
9+
"""Test fixture that returns the pin map directory."""
10+
return test_assets_directory / "acceptance" / "session_management"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import pathlib
2+
from typing import Generator, Iterable, NamedTuple
3+
4+
import pytest
5+
6+
from ni_measurementlink_service._internal.stubs.ni.measurementlink.measurement.v2.measurement_service_pb2 import (
7+
MeasureRequest,
8+
)
9+
from ni_measurementlink_service._internal.stubs.ni.measurementlink.measurement.v2.measurement_service_pb2_grpc import (
10+
MeasurementServiceStub,
11+
)
12+
from ni_measurementlink_service._internal.stubs.ni.measurementlink.pin_map_context_pb2 import (
13+
PinMapContext,
14+
)
15+
from ni_measurementlink_service.measurement.service import MeasurementService
16+
from tests.assets.stubs.nidcpower_measurement.types_pb2 import (
17+
Configurations,
18+
Outputs,
19+
)
20+
from tests.utilities import nidcpower_measurement
21+
from tests.utilities.pin_map_client import PinMapClient
22+
23+
_SITE = 0
24+
25+
26+
def test___single_session___measure___returns_measured_values(
27+
pin_map_context: PinMapContext,
28+
stub_v2: MeasurementServiceStub,
29+
) -> None:
30+
configurations = Configurations(pin_names=["Pin1"], multi_session=False)
31+
32+
outputs = _measure(stub_v2, pin_map_context, configurations)
33+
34+
assert outputs.voltage_measurements == [5]
35+
assert outputs.current_measurements == [0.0001]
36+
37+
38+
def test___single_session___measure___creates_single_session(
39+
pin_map_context: PinMapContext,
40+
stub_v2: MeasurementServiceStub,
41+
) -> None:
42+
configurations = Configurations(pin_names=["Pin1"], multi_session=False)
43+
44+
outputs = _measure(stub_v2, pin_map_context, configurations)
45+
46+
assert _get_output(outputs) == [
47+
_MeasurementOutput("DCPower1/0", "DCPower1/0", "DCPower1/0", "DCPower1/0")
48+
]
49+
50+
51+
def test___multiple_sessions___measure___creates_multiple_sessions(
52+
pin_map_context: PinMapContext,
53+
stub_v2: MeasurementServiceStub,
54+
) -> None:
55+
configurations = Configurations(pin_names=["Pin1", "Pin2"], multi_session=True)
56+
57+
outputs = _measure(stub_v2, pin_map_context, configurations)
58+
59+
assert _get_output(outputs) == [
60+
_MeasurementOutput("DCPower1/0", "DCPower1/0", "DCPower1/0", "DCPower1/0"),
61+
_MeasurementOutput("DCPower1/2", "DCPower1/2", "DCPower1/2", "DCPower1/2"),
62+
]
63+
64+
65+
def _measure(
66+
stub_v2: MeasurementServiceStub,
67+
pin_map_context: PinMapContext,
68+
configurations: Configurations,
69+
) -> Outputs:
70+
request = MeasureRequest(pin_map_context=pin_map_context)
71+
request.configuration_parameters.Pack(configurations)
72+
response_iterator = stub_v2.Measure(request)
73+
responses = list(response_iterator)
74+
assert len(responses) == 1
75+
outputs = Outputs.FromString(responses[0].outputs.value)
76+
return outputs
77+
78+
79+
@pytest.fixture(scope="module")
80+
def measurement_service() -> Generator[MeasurementService, None, None]:
81+
"""Test fixture that creates and hosts a measurement service."""
82+
with nidcpower_measurement.measurement_service.host_service() as service:
83+
yield service
84+
85+
86+
@pytest.fixture
87+
def pin_map_context(pin_map_client: PinMapClient, pin_map_directory: pathlib.Path) -> PinMapContext:
88+
pin_map_name = "1Smu2ChannelGroup2Pin1Site.pinmap"
89+
pin_map_id = pin_map_client.update_pin_map(pin_map_directory / pin_map_name)
90+
91+
return PinMapContext(pin_map_id=pin_map_id, sites=[_SITE])
92+
93+
94+
class _MeasurementOutput(NamedTuple):
95+
session_name: str
96+
resource_name: str
97+
channel_list: str
98+
connected_channels: str
99+
100+
101+
def _get_output(outputs: Outputs) -> Iterable[_MeasurementOutput]:
102+
return [
103+
_MeasurementOutput(session_name, resource_name, channel_list, connected_channels)
104+
for session_name, resource_name, channel_list, connected_channels in zip(
105+
outputs.session_names,
106+
outputs.resource_names,
107+
outputs.channel_lists,
108+
outputs.connected_channels,
109+
)
110+
]

tests/acceptance/test_session_management.py

-6
Original file line numberDiff line numberDiff line change
@@ -297,9 +297,3 @@ def measurement_service(
297297
"""Test fixture that creates and hosts a measurement service."""
298298
with pin_aware_measurement.measurement_service.host_service() as service:
299299
yield service
300-
301-
302-
@pytest.fixture
303-
def pin_map_directory(test_assets_directory: pathlib.Path) -> pathlib.Path:
304-
"""Test fixture that returns the pin map directory."""
305-
return test_assets_directory / "acceptance" / "session_management"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<PinMap xmlns="http://www.ni.com/TestStand/SemiconductorModule/PinMap.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" schemaVersion="1.6">
3+
<Instruments>
4+
<NIDCPowerInstrument name="DCPower1" numberOfChannels="4">
5+
<ChannelGroup name="DCPowerChannelGroup0" channels="0,1" />
6+
<ChannelGroup name="DCPowerChannelGroup1" channels="2,3" />
7+
</NIDCPowerInstrument>
8+
</Instruments>
9+
<Pins>
10+
<DUTPin name="Pin1" />
11+
<DUTPin name="Pin2" />
12+
</Pins>
13+
<PinGroups></PinGroups>
14+
<Sites>
15+
<Site siteNumber="0" />
16+
</Sites>
17+
<Connections>
18+
<Connection pin="Pin1" siteNumber="0" instrument="DCPower1" channel="0" />
19+
<Connection pin="Pin2" siteNumber="0" instrument="DCPower1" channel="2" />
20+
</Connections>
21+
</PinMap>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<PinMap xmlns="http://www.ni.com/TestStand/SemiconductorModule/PinMap.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" schemaVersion="1.6">
3+
<Instruments>
4+
<NIDCPowerInstrument name="DCPower1" numberOfChannels="4">
5+
<ChannelGroup name="DCPowerChannelGroup0" channels="0,1" />
6+
<ChannelGroup name="DCPowerChannelGroup1" channels="2,3" />
7+
</NIDCPowerInstrument>
8+
</Instruments>
9+
<Pins>
10+
<DUTPin name="Pin1" />
11+
<DUTPin name="Pin2" />
12+
</Pins>
13+
<PinGroups></PinGroups>
14+
<Sites>
15+
<Site siteNumber="0" />
16+
</Sites>
17+
<Connections>
18+
<Connection pin="Pin1" siteNumber="0" instrument="DCPower1" channel="0" />
19+
<Connection pin="Pin2" siteNumber="0" instrument="DCPower1" channel="2" />
20+
</Connections>
21+
</PinMap>

tests/assets/stubs/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Auto generated gRPC files."""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Auto generated gRPC files for nidcpower test measurement."""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
syntax = "proto3";
2+
package ni.measurementlink.measurement.tests.nidcpower_measurement;
3+
4+
message Configurations {
5+
repeated string pin_names = 1;
6+
bool multi_session = 2;
7+
}
8+
9+
message Outputs {
10+
repeated string session_names = 1;
11+
repeated string resource_names = 2;
12+
repeated string channel_lists = 3;
13+
repeated string connected_channels = 4;
14+
repeated double voltage_measurements = 5;
15+
repeated double current_measurements = 6;
16+
}

tests/assets/stubs/nidcpower_measurement/types_pb2.py

+27
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""
2+
@generated by mypy-protobuf. Do not edit manually!
3+
isort:skip_file
4+
"""
5+
import builtins
6+
import collections.abc
7+
import google.protobuf.descriptor
8+
import google.protobuf.internal.containers
9+
import google.protobuf.message
10+
import sys
11+
12+
if sys.version_info >= (3, 8):
13+
import typing as typing_extensions
14+
else:
15+
import typing_extensions
16+
17+
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
18+
19+
@typing_extensions.final
20+
class Configurations(google.protobuf.message.Message):
21+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
22+
23+
PIN_NAMES_FIELD_NUMBER: builtins.int
24+
MULTI_SESSION_FIELD_NUMBER: builtins.int
25+
@property
26+
def pin_names(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ...
27+
multi_session: builtins.bool
28+
def __init__(
29+
self,
30+
*,
31+
pin_names: collections.abc.Iterable[builtins.str] | None = ...,
32+
multi_session: builtins.bool = ...,
33+
) -> None: ...
34+
def ClearField(self, field_name: typing_extensions.Literal["multi_session", b"multi_session", "pin_names", b"pin_names"]) -> None: ...
35+
36+
global___Configurations = Configurations
37+
38+
@typing_extensions.final
39+
class Outputs(google.protobuf.message.Message):
40+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
41+
42+
SESSION_NAMES_FIELD_NUMBER: builtins.int
43+
RESOURCE_NAMES_FIELD_NUMBER: builtins.int
44+
CHANNEL_LISTS_FIELD_NUMBER: builtins.int
45+
CONNECTED_CHANNELS_FIELD_NUMBER: builtins.int
46+
VOLTAGE_MEASUREMENTS_FIELD_NUMBER: builtins.int
47+
CURRENT_MEASUREMENTS_FIELD_NUMBER: builtins.int
48+
@property
49+
def session_names(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ...
50+
@property
51+
def resource_names(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ...
52+
@property
53+
def channel_lists(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ...
54+
@property
55+
def connected_channels(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ...
56+
@property
57+
def voltage_measurements(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.float]: ...
58+
@property
59+
def current_measurements(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.float]: ...
60+
def __init__(
61+
self,
62+
*,
63+
session_names: collections.abc.Iterable[builtins.str] | None = ...,
64+
resource_names: collections.abc.Iterable[builtins.str] | None = ...,
65+
channel_lists: collections.abc.Iterable[builtins.str] | None = ...,
66+
connected_channels: collections.abc.Iterable[builtins.str] | None = ...,
67+
voltage_measurements: collections.abc.Iterable[builtins.float] | None = ...,
68+
current_measurements: collections.abc.Iterable[builtins.float] | None = ...,
69+
) -> None: ...
70+
def ClearField(self, field_name: typing_extensions.Literal["channel_lists", b"channel_lists", "connected_channels", b"connected_channels", "current_measurements", b"current_measurements", "resource_names", b"resource_names", "session_names", b"session_names", "voltage_measurements", b"voltage_measurements"]) -> None: ...
71+
72+
global___Outputs = Outputs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
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+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""
2+
@generated by mypy-protobuf. Do not edit manually!
3+
isort:skip_file
4+
"""
5+
import abc
6+
import collections.abc
7+
import grpc
8+
import grpc.aio
9+
import typing
10+
11+
_T = typing.TypeVar('_T')
12+
13+
class _MaybeAsyncIterator(collections.abc.AsyncIterator[_T], collections.abc.Iterator[_T], metaclass=abc.ABCMeta):
14+
...
15+
16+
class _ServicerContext(grpc.ServicerContext, grpc.aio.ServicerContext): # type: ignore
17+
...
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Integration tests for driver-specific session management APIs."""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""Pytest configuration file for integration tests."""
2+
import pathlib
3+
4+
import pytest
5+
6+
7+
@pytest.fixture(scope="module")
8+
def pin_map_directory(test_assets_directory: pathlib.Path) -> pathlib.Path:
9+
"""Test fixture that returns the pin map directory."""
10+
return test_assets_directory / "integration" / "session_management"

0 commit comments

Comments
 (0)