Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
568fce9
Add new TLS system tests
ryanwixon-emerson Sep 9, 2026
e1f346b
Add sad path tests
ryanwixon-emerson Sep 10, 2026
40140b9
Resolve flake errors
ryanwixon-emerson Sep 10, 2026
e771017
Tests are passing locally
ryanwixon-emerson Sep 11, 2026
09768c6
Rerun system tests
ryanwixon-emerson Sep 14, 2026
62e5ef1
Path client config, slight test tweaks
ryanwixon-emerson Sep 14, 2026
d35c662
Remove unusued import
ryanwixon-emerson Sep 14, 2026
8e7ba3b
[TEMP] Revert exchange_certs patch
ryanwixon-emerson Sep 14, 2026
f74b0cd
Change exchange_certificates invocation
ryanwixon-emerson Sep 14, 2026
30aa42b
Try to set user environemnt var manually
ryanwixon-emerson Sep 14, 2026
deee6d7
Fix Assertion problem and no-op nitlsconfigtest stuff on Linux
ryanwixon-emerson Sep 14, 2026
d05a0d3
Formatter fix
ryanwixon-emerson Sep 14, 2026
09233f9
Use sysnative so 32 bit tests can see nitlsconfig
ryanwixon-emerson Sep 14, 2026
6a832f8
Fix warnings
ryanwixon-emerson Sep 14, 2026
e4e608c
[Experimental] Try force disabling WOW64 redirection
ryanwixon-emerson Sep 14, 2026
b8806ec
[Experimental] Process wide redirection disabled
ryanwixon-emerson Sep 14, 2026
c85077d
[Experimental] Claude's "validated" fix?
ryanwixon-emerson Sep 14, 2026
fb07473
Clean up implementation (fully working?)
ryanwixon-emerson Sep 14, 2026
eff59b6
Rereun flakey test
ryanwixon-emerson Sep 15, 2026
d5d03d8
Run flakey test again
ryanwixon-emerson Sep 15, 2026
1ac846a
Run flakey test again
ryanwixon-emerson Sep 15, 2026
a538bf5
Implement nidcpower system tests
ryanwixon-emerson Sep 15, 2026
859f0d5
Implement nidigital and nifgen system tests
ryanwixon-emerson Sep 15, 2026
3e003c5
Add system tests for all remaining drivers
ryanwixon-emerson Sep 15, 2026
671cac0
Reorder tests
ryanwixon-emerson Sep 15, 2026
315c910
Refactor tests to address comments
ryanwixon-emerson Sep 16, 2026
b3710c7
Fix flake failure
ryanwixon-emerson Sep 16, 2026
8e2a08d
Flip the type of tests that run the full suite
ryanwixon-emerson Sep 16, 2026
984c5e8
Don't use nitlsconfig channel for NoTLS test
ryanwixon-emerson Sep 16, 2026
f46d93e
Now disable on 32-bit
ryanwixon-emerson Sep 16, 2026
00a3c96
Various improvements to address review comments
ryanwixon-emerson Sep 17, 2026
a87a556
Fix flake
ryanwixon-emerson Sep 17, 2026
6451175
Merge branch 'addTLS' into users/rwixon/addAllTLS
ryanwixon-emerson Sep 17, 2026
6b7dbca
Refactor to match the DMM changes
ryanwixon-emerson Sep 17, 2026
bbc22cd
Merge branch 'master' of https://github.com/ni/nimi-python into addAl…
ryanwixon-emerson Sep 18, 2026
bb9d875
Revert test migration
ryanwixon-emerson Sep 18, 2026
c62d4c5
Rework which tests get put into BasicValidationTests
ryanwixon-emerson Sep 18, 2026
63ddf2d
Minor fixes
ryanwixon-emerson Sep 18, 2026
f8db1bb
Fix CI failures
ryanwixon-emerson Sep 18, 2026
1b2cee9
Fix flake
ryanwixon-emerson Sep 18, 2026
4cd1617
Another flake error
ryanwixon-emerson Sep 18, 2026
3b06fb2
Another CI failure
ryanwixon-emerson Sep 18, 2026
6fb6f69
Yet another flake error
ryanwixon-emerson Sep 18, 2026
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
8 changes: 8 additions & 0 deletions src/nidcpower/system_tests/grpc_server_config_tls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"address": "[::]",
"port": 31760,
"security": "ni-tls-config",
"feature_toggles": {
"ni-tls-config": true
}
}
145 changes: 97 additions & 48 deletions src/nidcpower/system_tests/test_system_nidcpower.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import grpc
import hightime
import nitlsconfig
import pytest

import nidcpower
Expand Down Expand Up @@ -37,7 +38,9 @@ def pytest_generate_tests(metafunc):
metafunc.parametrize('session', [True], indirect=True)


class SystemTests:
# Defines a subset of system tests to validate basic NI-DCPower functionality. This is run as a part of the full SystemTests class, and

@ni-jfitzger ni-jfitzger Sep 20, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say this use of "NI-DCPower" is slightly inaccurate. The purpose of our nimi-python system tests is to validate the API, not the driver. As part of that, we do make actual driver calls, but validating the driver functionality would be much more complicated and generally requires real hardware.

The driver is NI-DCPower, but the Python API is nidcpower.
This comment applies to all of the system tests, you're touching, as well as nidmm system tests.

# independently for test classes which do not require running the entire suite (TLS-enabled gRPC tests today).
class BasicValidationTests:
@pytest.fixture(scope='function')
def session(self, request, session_creation_kwargs):
"""Creates an NI-DCPower Session.
Expand Down Expand Up @@ -78,6 +81,50 @@ def session(self, request, session_creation_kwargs):
def test_self_test(self, session):
session.self_test()

@pytest.mark.channels('0')
def test_measure(self, session):
session.source_mode = nidcpower.SourceMode.SINGLE_POINT
session.output_function = nidcpower.OutputFunction.DC_VOLTAGE
session.voltage_level_range = 6
session.voltage_level = 2
with session.initiate():
reading = session.measure(nidcpower.MeasurementTypes.VOLTAGE)
assert session.query_in_compliance() is False
assert reading == 2

@pytest.mark.channels('0')
def test_fetch_multiple(self, session):
session.source_mode = nidcpower.SourceMode.SINGLE_POINT
session.configure_aperture_time(0, nidcpower.ApertureTimeUnits.SECONDS)
session.voltage_level = 1
count = 10
session.measure_when = nidcpower.MeasureWhen.AUTOMATICALLY_AFTER_SOURCE_COMPLETE
with session.initiate():
measurements = session.fetch_multiple(count)
assert len(measurements) == count
assert isinstance(measurements[1].voltage, float)
assert isinstance(measurements[1].current, float)
assert measurements[1].in_compliance in [True, False]
assert measurements[1].voltage == 1.0
assert measurements[1].current == 0.00001

def test_measure_multiple(self, session):
with session.initiate():
# session is open to all 12 channels on the device
measurements = session.measure_multiple()
assert len(measurements) == 12
assert measurements[1].in_compliance is None
assert measurements[1].voltage == 0.0
assert measurements[1].current == 0.00001
# now a subset of the channels
measurements = session.channels[range(4)].measure_multiple()
assert len(measurements) == 4
assert measurements[1].in_compliance is None
assert measurements[1].voltage == 0.0
assert measurements[1].current == 0.00001


class SystemTests(BasicValidationTests):
# Workaround for driver runtime bug. See issue #1798 for details.
@pytest.mark.legacy_session_only
def test_self_cal(self, session):
Expand Down Expand Up @@ -161,17 +208,6 @@ def test_disable(self, session):
session.disable()
assert channel.output_enabled is False

@pytest.mark.channels('0')
def test_measure(self, session):
session.source_mode = nidcpower.SourceMode.SINGLE_POINT
session.output_function = nidcpower.OutputFunction.DC_VOLTAGE
session.voltage_level_range = 6
session.voltage_level = 2
with session.initiate():
reading = session.measure(nidcpower.MeasurementTypes.VOLTAGE)
assert session.query_in_compliance() is False
assert reading == 2

@pytest.mark.channels('0')
def test_query_output_state(self, session):
with session.initiate():
Expand All @@ -192,37 +228,6 @@ def test_config_aperture_time(self, session):
aperture_time_in_range = abs(aperture_time - expected_aperture_time) <= max(1e-09 * max(abs(aperture_time), abs(expected_aperture_time)), 0.0) # https://stackoverflow.com/questions/5595425/what-is-the-best-way-to-compare-floats-for-almost-equality-in-python
assert aperture_time_in_range is True

@pytest.mark.channels('0')
def test_fetch_multiple(self, session):
session.source_mode = nidcpower.SourceMode.SINGLE_POINT
session.configure_aperture_time(0, nidcpower.ApertureTimeUnits.SECONDS)
session.voltage_level = 1
count = 10
session.measure_when = nidcpower.MeasureWhen.AUTOMATICALLY_AFTER_SOURCE_COMPLETE
with session.initiate():
measurements = session.fetch_multiple(count)
assert len(measurements) == count
assert isinstance(measurements[1].voltage, float)
assert isinstance(measurements[1].current, float)
assert measurements[1].in_compliance in [True, False]
assert measurements[1].voltage == 1.0
assert measurements[1].current == 0.00001

def test_measure_multiple(self, session):
with session.initiate():
# session is open to all 12 channels on the device
measurements = session.measure_multiple()
assert len(measurements) == 12
assert measurements[1].in_compliance is None
assert measurements[1].voltage == 0.0
assert measurements[1].current == 0.00001
# now a subset of the channels
measurements = session.channels[range(4)].measure_multiple()
assert len(measurements) == 4
assert measurements[1].in_compliance is None
assert measurements[1].voltage == 0.0
assert measurements[1].current == 0.00001

@pytest.mark.parametrize(
'resource_name,channels,independent_channels,measurement_channels,expected_measured_channel',
[
Expand Down Expand Up @@ -1074,7 +1079,8 @@ def test_multi_threading_ivi_synchronized_wrapper_releases_lock(self, session):

class TestLibrary(SystemTests):
@pytest.fixture(scope='class')
def session_creation_kwargs(self):
@classmethod
def session_creation_kwargs(cls):
return {}

@pytest.mark.resource_name("4190/0")
Expand All @@ -1096,17 +1102,19 @@ def test_lcr_compensation_data(self, session):
session.configure_lcr_compensation(compensation_data_bytes_from_file)


class TestGrpc(SystemTests):
class TestGrpcNoTLS(SystemTests):
@pytest.fixture(scope='class')
def grpc_channel(self):
@classmethod
def grpc_channel(cls):
current_directory = os.path.dirname(os.path.abspath(__file__))
config_file_path = os.path.join(current_directory, 'grpc_server_config.json')
config_file_path = os.path.join(current_directory, 'grpc_server_config_no_tls.json')
with system_test_utilities.GrpcServerProcess(config_file_path) as proc:
channel = grpc.insecure_channel(f"localhost:{proc.server_port}")
yield channel

@pytest.fixture(scope='class')
def session_creation_kwargs(self, grpc_channel):
@classmethod
def session_creation_kwargs(cls, grpc_channel):
grpc_options = nidcpower.GrpcSessionOptions(grpc_channel, "")
return {'grpc_options': grpc_options}

Expand All @@ -1121,3 +1129,44 @@ def test_configure_lcr_compensation(self, session):
session.configure_lcr_compensation([])
assert exc_info.value.args[0] == 'configure_lcr_compensation is not supported over gRPC'
assert str(exc_info.value) == 'configure_lcr_compensation is not supported over gRPC'


@pytest.mark.skipif(sys.maxsize < 2**32, reason="TLS configuration and certificate exchange scripts are not supported in 32-bit processes")
class TestGrpcSecuredTLS(BasicValidationTests):
@pytest.fixture(scope='class')
@classmethod
def grpc_channel(cls):
system_test_utilities.configure_tls_modes_secure(service="ni-grpc-device", server_host="localhost")
system_test_utilities.exchange_certificates("localhost")

current_directory = os.path.dirname(os.path.abspath(__file__))
config_file_path = os.path.join(current_directory, 'grpc_server_config_tls.json')
with system_test_utilities.GrpcServerProcess(config_file_path) as proc:
channel = nitlsconfig.create_grpc_device_channel('localhost', proc.server_port)
yield channel

@pytest.fixture(scope='class')
@classmethod
def session_creation_kwargs(cls, grpc_channel):
grpc_options = nidcpower.GrpcSessionOptions(grpc_channel, "")
return {'grpc_options': grpc_options}


@pytest.mark.skipif(sys.maxsize < 2**32, reason="TLS configuration and certificate exchange scripts are not supported in 32-bit processes")
class TestGrpcUnsecuredTLS(BasicValidationTests):
@pytest.fixture(scope='class')
@classmethod
def grpc_channel(cls):
system_test_utilities.configure_tls_modes_insecure(service="ni-grpc-device", server_host="localhost")

current_directory = os.path.dirname(os.path.abspath(__file__))
config_file_path = os.path.join(current_directory, 'grpc_server_config_tls.json')
with system_test_utilities.GrpcServerProcess(config_file_path) as proc:
channel = nitlsconfig.create_grpc_device_channel('localhost', proc.server_port)
yield channel

@pytest.fixture(scope='class')
@classmethod
def session_creation_kwargs(cls, grpc_channel):
grpc_options = nidcpower.GrpcSessionOptions(grpc_channel, "")
return {'grpc_options': grpc_options}
8 changes: 8 additions & 0 deletions src/nidigital/system_tests/grpc_server_config_tls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"address": "[::]",
"port": 31761,
"security": "ni-tls-config",
"feature_toggles": {
"ni-tls-config": true
}
}
Loading
Loading