Skip to content

Commit f26fb49

Browse files
tests: Add integration and acceptance tests for NI-FGEN and NI-SCOPE drivers session management APIs (#506)
* tests: add files to write tests for fgen session apis * tests: add files to write tests for niscope session apis * tests: add nifgen test measurement service * tests: add niscope test measurement service * tests: add integration tests for nifgen session APIs * tests: add integration tests for niscope session APIs * tests: add proto and stubs for nifgen measurement * tests: add proto and stubs for niscope measurement * tests: add acceptance tests for nifgen driver session apis * tests: add acceptance tests for niscope driver sessions apis * tests: add wait until logic for non-simulation * refactor: niscope test measurement * fix: remove unnecessary lines * refactor: assert measured values within a range * tests: add init files to the stubs directory * tests: remove wait code & standardize terminology
1 parent f8ea647 commit f26fb49

24 files changed

Lines changed: 929 additions & 0 deletions
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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.nifgen_measurement.types_pb2 import (
17+
Configurations,
18+
Outputs,
19+
)
20+
from tests.utilities import nifgen_measurement
21+
from tests.utilities.pin_map_client import PinMapClient
22+
23+
_SITE = 0
24+
25+
26+
def test___single_session___measure___creates_single_session(
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 _get_output(outputs) == [_MeasurementOutput("FGEN1", "FGEN1", "0", "0")]
35+
36+
37+
def test___multiple_sessions___measure___creates_multiple_sessions(
38+
pin_map_context: PinMapContext,
39+
stub_v2: MeasurementServiceStub,
40+
) -> None:
41+
configurations = Configurations(pin_names=["Pin1", "Pin2"], multi_session=True)
42+
43+
outputs = _measure(stub_v2, pin_map_context, configurations)
44+
45+
assert _get_output(outputs) == [
46+
_MeasurementOutput("FGEN1", "FGEN1", "0", "0"),
47+
_MeasurementOutput("FGEN2", "FGEN2", "0", "0"),
48+
]
49+
50+
51+
def _measure(
52+
stub_v2: MeasurementServiceStub,
53+
pin_map_context: PinMapContext,
54+
configurations: Configurations,
55+
) -> Outputs:
56+
request = MeasureRequest(pin_map_context=pin_map_context)
57+
request.configuration_parameters.Pack(configurations)
58+
response_iterator = stub_v2.Measure(request)
59+
responses = list(response_iterator)
60+
assert len(responses) == 1
61+
outputs = Outputs.FromString(responses[0].outputs.value)
62+
return outputs
63+
64+
65+
@pytest.fixture(scope="module")
66+
def measurement_service() -> Generator[MeasurementService, None, None]:
67+
"""Test fixture that creates and hosts a measurement service."""
68+
with nifgen_measurement.measurement_service.host_service() as service:
69+
yield service
70+
71+
72+
@pytest.fixture
73+
def pin_map_context(pin_map_client: PinMapClient, pin_map_directory: pathlib.Path) -> PinMapContext:
74+
pin_map_name = "2Fgen2Pin1Site.pinmap"
75+
pin_map_id = pin_map_client.update_pin_map(pin_map_directory / pin_map_name)
76+
77+
return PinMapContext(pin_map_id=pin_map_id, sites=[_SITE])
78+
79+
80+
class _MeasurementOutput(NamedTuple):
81+
session_name: str
82+
resource_name: str
83+
channel_list: str
84+
connected_channels: str
85+
86+
87+
def _get_output(outputs: Outputs) -> Iterable[_MeasurementOutput]:
88+
return [
89+
_MeasurementOutput(session_name, resource_name, channel_list, connected_channels)
90+
for session_name, resource_name, channel_list, connected_channels in zip(
91+
outputs.session_names,
92+
outputs.resource_names,
93+
outputs.channel_lists,
94+
outputs.connected_channels,
95+
)
96+
]
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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.niscope_measurement.types_pb2 import (
17+
Configurations,
18+
Outputs,
19+
)
20+
from tests.utilities import niscope_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 all([-2.5 < sample < 2.5 for sample in outputs.waveform])
35+
36+
37+
def test___single_session___measure___creates_single_session(
38+
pin_map_context: PinMapContext,
39+
stub_v2: MeasurementServiceStub,
40+
) -> None:
41+
configurations = Configurations(pin_names=["Pin1"], multi_session=False)
42+
43+
outputs = _measure(stub_v2, pin_map_context, configurations)
44+
45+
assert _get_output(outputs) == [_MeasurementOutput("SCOPE1", "SCOPE1", "0", "0")]
46+
47+
48+
def test___multiple_sessions___measure___creates_multiple_sessions(
49+
pin_map_context: PinMapContext,
50+
stub_v2: MeasurementServiceStub,
51+
) -> None:
52+
configurations = Configurations(pin_names=["Pin1", "Pin2"], multi_session=True)
53+
54+
outputs = _measure(stub_v2, pin_map_context, configurations)
55+
56+
assert _get_output(outputs) == [
57+
_MeasurementOutput("SCOPE1", "SCOPE1", "0", "0"),
58+
_MeasurementOutput("SCOPE2", "SCOPE2", "0", "0"),
59+
]
60+
61+
62+
def _measure(
63+
stub_v2: MeasurementServiceStub,
64+
pin_map_context: PinMapContext,
65+
configurations: Configurations,
66+
) -> Outputs:
67+
request = MeasureRequest(pin_map_context=pin_map_context)
68+
request.configuration_parameters.Pack(configurations)
69+
response_iterator = stub_v2.Measure(request)
70+
responses = list(response_iterator)
71+
assert len(responses) == 1
72+
outputs = Outputs.FromString(responses[0].outputs.value)
73+
return outputs
74+
75+
76+
@pytest.fixture(scope="module")
77+
def measurement_service() -> Generator[MeasurementService, None, None]:
78+
"""Test fixture that creates and hosts a measurement service."""
79+
with niscope_measurement.measurement_service.host_service() as service:
80+
yield service
81+
82+
83+
@pytest.fixture
84+
def pin_map_context(pin_map_client: PinMapClient, pin_map_directory: pathlib.Path) -> PinMapContext:
85+
pin_map_name = "2Scope2Pin2Group1Site.pinmap"
86+
pin_map_id = pin_map_client.update_pin_map(pin_map_directory / pin_map_name)
87+
88+
return PinMapContext(pin_map_id=pin_map_id, sites=[_SITE])
89+
90+
91+
class _MeasurementOutput(NamedTuple):
92+
session_name: str
93+
resource_name: str
94+
channel_list: str
95+
connected_channels: str
96+
97+
98+
def _get_output(outputs: Outputs) -> Iterable[_MeasurementOutput]:
99+
return [
100+
_MeasurementOutput(session_name, resource_name, channel_list, connected_channels)
101+
for session_name, resource_name, channel_list, connected_channels in zip(
102+
outputs.session_names,
103+
outputs.resource_names,
104+
outputs.channel_lists,
105+
outputs.connected_channels,
106+
)
107+
]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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.1">
3+
<Instruments>
4+
<NIFGenInstrument name="FGEN1" numberOfChannels="1" />
5+
<NIFGenInstrument name="FGEN2" numberOfChannels="1" />
6+
</Instruments>
7+
<Pins>
8+
<DUTPin name="Pin1" />
9+
<DUTPin name="Pin2" />
10+
</Pins>
11+
<PinGroups></PinGroups>
12+
<Sites>
13+
<Site siteNumber="0" />
14+
</Sites>
15+
<Connections>
16+
<Connection pin="Pin1" siteNumber="0" instrument="FGEN1" channel="0" />
17+
<Connection pin="Pin2" siteNumber="0" instrument="FGEN2" channel="0" />
18+
</Connections>
19+
</PinMap>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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.5">
3+
<Instruments>
4+
<NIScopeInstrument name="SCOPE1" numberOfChannels="4" group="Scope0" />
5+
<NIScopeInstrument name="SCOPE2" numberOfChannels="4" group="Scope1" />
6+
</Instruments>
7+
<Pins>
8+
<DUTPin name="Pin1" />
9+
<DUTPin name="Pin2" />
10+
</Pins>
11+
<PinGroups></PinGroups>
12+
<Sites>
13+
<Site siteNumber="0" />
14+
</Sites>
15+
<Connections>
16+
<Connection pin="Pin1" siteNumber="0" instrument="SCOPE1" channel="0" />
17+
<Connection pin="Pin2" siteNumber="0" instrument="SCOPE2" channel="0" />
18+
</Connections>
19+
</PinMap>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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.1">
3+
<Instruments>
4+
<NIFGenInstrument name="FGEN1" numberOfChannels="1" />
5+
<NIFGenInstrument name="FGEN2" numberOfChannels="1" />
6+
</Instruments>
7+
<Pins>
8+
<DUTPin name="Pin1" />
9+
<DUTPin name="Pin2" />
10+
</Pins>
11+
<PinGroups></PinGroups>
12+
<Sites>
13+
<Site siteNumber="0" />
14+
</Sites>
15+
<Connections>
16+
<Connection pin="Pin1" siteNumber="0" instrument="FGEN1" channel="0" />
17+
<Connection pin="Pin2" siteNumber="0" instrument="FGEN2" channel="0" />
18+
</Connections>
19+
</PinMap>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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.5">
3+
<Instruments>
4+
<NIScopeInstrument name="SCOPE1" numberOfChannels="4" group="Scope0" />
5+
<NIScopeInstrument name="SCOPE2" numberOfChannels="4" group="Scope1" />
6+
</Instruments>
7+
<Pins>
8+
<DUTPin name="Pin1" />
9+
<DUTPin name="Pin2" />
10+
</Pins>
11+
<PinGroups></PinGroups>
12+
<Sites>
13+
<Site siteNumber="0" />
14+
</Sites>
15+
<Connections>
16+
<Connection pin="Pin1" siteNumber="0" instrument="SCOPE1" channel="0" />
17+
<Connection pin="Pin2" siteNumber="0" instrument="SCOPE2" channel="0" />
18+
</Connections>
19+
</PinMap>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Auto generated gRPC files for nifgen test measurement."""
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
syntax = "proto3";
2+
package ni.measurementlink.measurement.tests.nifgen_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+
}

tests/assets/stubs/nifgen_measurement/types_pb2.py

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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+
@property
47+
def session_names(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ...
48+
@property
49+
def resource_names(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ...
50+
@property
51+
def channel_lists(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ...
52+
@property
53+
def connected_channels(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ...
54+
def __init__(
55+
self,
56+
*,
57+
session_names: collections.abc.Iterable[builtins.str] | None = ...,
58+
resource_names: collections.abc.Iterable[builtins.str] | None = ...,
59+
channel_lists: collections.abc.Iterable[builtins.str] | None = ...,
60+
connected_channels: collections.abc.Iterable[builtins.str] | None = ...,
61+
) -> None: ...
62+
def ClearField(self, field_name: typing_extensions.Literal["channel_lists", b"channel_lists", "connected_channels", b"connected_channels", "resource_names", b"resource_names", "session_names", b"session_names"]) -> None: ...
63+
64+
global___Outputs = Outputs

0 commit comments

Comments
 (0)