diff --git a/.github/workflows/run_unit_tests_oldest_deps.yml b/.github/workflows/run_unit_tests_oldest_deps.yml index abaa45ef..53c74c0a 100644 --- a/.github/workflows/run_unit_tests_oldest_deps.yml +++ b/.github/workflows/run_unit_tests_oldest_deps.yml @@ -45,7 +45,8 @@ jobs: - name: Display installed dependency versions run: poetry run pip list - name: Run unit tests and code coverage - run: poetry run pytest ./tests/unit -v --cov=nitypes --junitxml=test_results/nitypes-oldest-deps-${{ matrix.os }}-py${{ matrix.python-version }}.xml + # Skip test___pickled_value___unpickle___is_compatible because this test's pickle data was saved with NumPy 2.x and cannot be loaded in NumPy 1.x. + run: poetry run pytest ./tests/unit -v --cov=nitypes --junitxml=test_results/nitypes-oldest-deps-${{ matrix.os }}-py${{ matrix.python-version }}.xml -k "not test___pickled_value___unpickle___is_compatible" - name: Upload test results uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: diff --git a/tests/unit/bintime/test_datetime.py b/tests/unit/bintime/test_datetime.py index 77002d00..afab12f1 100644 --- a/tests/unit/bintime/test_datetime.py +++ b/tests/unit/bintime/test_datetime.py @@ -447,6 +447,27 @@ def test___time_value___pickle___references_public_modules() -> None: assert b"nitypes.bintime._time_value" not in value_bytes +@pytest.mark.parametrize( + "pickled_value, expected", + [ + # nitypes 1.0.0 + ( + b"\x80\x04\x95W\x00\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x07getattr\x94\x93\x94\x8c\x0fnitypes.bintime\x94\x8c\x08DateTime\x94\x93\x94\x8c\nfrom_ticks\x94\x86\x94R\x94\x8a\tN\xca\xb9\xf4\xe9\xd3\x9a\x1f\xff\x85\x94R\x94.", + DateTime(1903, 12, 31, 23, 59, 59, 123_456, 234_567_789, 345_567_890, dt.timezone.utc), + ), + ( + b"\x80\x04\x95[\x00\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x07getattr\x94\x93\x94\x8c\x0fnitypes.bintime\x94\x8c\x08DateTime\x94\x93\x94\x8c\nfrom_ticks\x94\x86\x94R\x94\x8a\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf4\x92\xb4\x00\x85\x94R\x94.", + DateTime(2000, 1, 1, 0, 0, 0, 0, 0, 0, dt.timezone.utc), + ), + ], +) +def test___pickled_value___unpickle___is_compatible( + pickled_value: bytes, expected: DateTime +) -> None: + new_value = pickle.loads(pickled_value) + assert new_value == expected + + @pytest.mark.parametrize( "value, expected", [ diff --git a/tests/unit/bintime/test_datetime_array.py b/tests/unit/bintime/test_datetime_array.py index 92d2d038..402e7e64 100644 --- a/tests/unit/bintime/test_datetime_array.py +++ b/tests/unit/bintime/test_datetime_array.py @@ -1322,6 +1322,29 @@ def test___datetime_array___pickle___references_public_modules() -> None: assert b"nitypes.bintime._datetime_array" not in pickled +@pytest.mark.parametrize( + "pickled_value, expected", + [ + # nitypes 1.0.0 + ( + b"\x80\x04\x95\xb1\x00\x00\x00\x00\x00\x00\x00\x8c\x0fnitypes.bintime\x94\x8c\rDateTimeArray\x94\x93\x94]\x94(\x8c\x08builtins\x94\x8c\x07getattr\x94\x93\x94h\x00\x8c\x08DateTime\x94\x93\x94\x8c\nfrom_ticks\x94\x86\x94R\x94\x8a\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf4\x92\xb4\x00\x85\x94R\x94h\x06h\x08h\t\x86\x94R\x94\x8a\r\x00\x00\x00\x00\x00\x00\x00\x00\x00N\xc4\xa1\x00\x85\x94R\x94h\x06h\x08h\t\x86\x94R\x94\x8a\r\x00\x00\x00\x00\x00\x00\x00\x00\x006\x9a\xe3\x00\x85\x94R\x94e\x85\x94R\x94.", + DateTimeArray( + [ + DateTime(2000, 1, 1, tzinfo=dt.timezone.utc), + DateTime(1990, 1, 1, tzinfo=dt.timezone.utc), + DateTime(2025, 1, 1, tzinfo=dt.timezone.utc), + ] + ), + ), + ], +) +def test___pickled_value___unpickle___is_compatible( + pickled_value: bytes, expected: DateTimeArray +) -> None: + new_value = pickle.loads(pickled_value) + assert new_value == expected + + @pytest.mark.parametrize( "value", ( diff --git a/tests/unit/bintime/test_timedelta.py b/tests/unit/bintime/test_timedelta.py index 791e62d8..820b86e0 100644 --- a/tests/unit/bintime/test_timedelta.py +++ b/tests/unit/bintime/test_timedelta.py @@ -1200,6 +1200,27 @@ def test___timedelta___pickle___references_public_modules() -> None: assert b"nitypes.bintime._timedelta" not in value_bytes +@pytest.mark.parametrize( + "pickled_value, expected", + [ + # nitypes 1.0.0 + ( + b"\x80\x04\x95_\x00\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x07getattr\x94\x93\x94\x8c\x0fnitypes.bintime\x94\x8c\tTimeDelta\x94\x93\x94\x8c\nfrom_ticks\x94\x86\x94R\x94\x8a\x10\x03\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x10\x85\x94R\x94.", + TimeDelta.from_ticks((1 << 124) + (2 << 64) + 3), + ), + ( + b"\x80\x04\x95_\x00\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x07getattr\x94\x93\x94\x8c\x0fnitypes.bintime\x94\x8c\tTimeDelta\x94\x93\x94\x8c\nfrom_ticks\x94\x86\x94R\x94\x8a\x10\xfd\xff\xff\xff\xff\xff\xff\xff\xfd\xff\xff\xff\xff\xff\xff\xef\x85\x94R\x94.", + TimeDelta.from_ticks((-1 << 124) + (-2 << 64) + -3), + ), + ], +) +def test___pickled_value___unpickle___is_compatible( + pickled_value: bytes, expected: TimeDelta +) -> None: + new_value = pickle.loads(pickled_value) + assert new_value == expected + + @pytest.mark.parametrize( "value, expected", [ diff --git a/tests/unit/bintime/test_timedelta_array.py b/tests/unit/bintime/test_timedelta_array.py index 899ff66f..698d97f1 100644 --- a/tests/unit/bintime/test_timedelta_array.py +++ b/tests/unit/bintime/test_timedelta_array.py @@ -8,8 +8,7 @@ import pytest from typing_extensions import assert_type -from nitypes.bintime import TimeDelta -from nitypes.bintime import TimeDeltaArray +from nitypes.bintime import TimeDelta, TimeDeltaArray ############################################################################### @@ -920,6 +919,23 @@ def test___timedelta_array___pickle___references_public_modules() -> None: assert b"nitypes.bintime._timedelta_array" not in pickled +@pytest.mark.parametrize( + "pickled_value, expected", + [ + # nitypes 1.0.0 + ( + b"\x80\x04\x95\xa8\x00\x00\x00\x00\x00\x00\x00\x8c\x0fnitypes.bintime\x94\x8c\x0eTimeDeltaArray\x94\x93\x94]\x94(\x8c\x08builtins\x94\x8c\x07getattr\x94\x93\x94h\x00\x8c\tTimeDelta\x94\x93\x94\x8c\nfrom_ticks\x94\x86\x94R\x94\x8a\t\x00\x00\xc3\xf5(\\\x8fB\x14\x85\x94R\x94h\x06h\x08h\t\x86\x94R\x94\x8a\t\x00\x00\x00\x00\x00\x00\x00\x00\xff\x85\x94R\x94h\x06h\x08h\t\x86\x94R\x94\x8a\n\x00\x00\x00\x00\x00\x00\x00\x00\xf4\x01\x85\x94R\x94e\x85\x94R\x94.", + TimeDeltaArray([TimeDelta(20.26), TimeDelta(-1), TimeDelta(500)]), + ), + ], +) +def test___pickled_value___unpickle___is_compatible( + pickled_value: bytes, expected: TimeDeltaArray +) -> None: + new_value = pickle.loads(pickled_value) + assert new_value == expected + + @pytest.mark.parametrize( "value", ( diff --git a/tests/unit/scalar/test_scalar.py b/tests/unit/scalar/test_scalar.py index 6c5d73ed..d7487965 100644 --- a/tests/unit/scalar/test_scalar.py +++ b/tests/unit/scalar/test_scalar.py @@ -302,3 +302,20 @@ def test___scalar___pickle___references_public_modules() -> None: assert b"nitypes.scalar" in value_bytes assert b"nitypes.scalar._scalar" not in value_bytes + + +@pytest.mark.parametrize( + "pickled_value, expected", + [ + # nitypes 1.0.0 + ( + b"\x80\x04\x952\x00\x00\x00\x00\x00\x00\x00\x8c\x0enitypes.scalar\x94\x8c\x06Scalar\x94\x93\x94G@4\x00\x00\x00\x00\x00\x00\x8c\x05watts\x94\x86\x94R\x94.", + Scalar(20.0, "watts"), + ), + ], +) +def test___pickled_value___unpickle___is_compatible( + pickled_value: bytes, expected: Scalar[Any] +) -> None: + new_value = pickle.loads(pickled_value) + assert new_value == expected diff --git a/tests/unit/vector/test_vector.py b/tests/unit/vector/test_vector.py index 5d706619..65cced64 100644 --- a/tests/unit/vector/test_vector.py +++ b/tests/unit/vector/test_vector.py @@ -417,3 +417,20 @@ def test___vector___pickle___references_public_modules() -> None: assert b"nitypes.vector" in value_bytes assert b"nitypes.vector._vector" not in value_bytes + + +@pytest.mark.parametrize( + "pickled_value, expected", + [ + # nitypes 1.0.0 + ( + b"\x80\x04\x95?\x00\x00\x00\x00\x00\x00\x00\x8c\x0enitypes.vector\x94\x8c\x06Vector\x94\x93\x94]\x94(G@4\x00\x00\x00\x00\x00\x00G@4\x19\x99\x99\x99\x99\x9ae\x8c\x05watts\x94\x86\x94R\x94.", + Vector([20.0, 20.1], "watts"), + ), + ], +) +def test___pickled_value___unpickle___is_compatible( + pickled_value: bytes, expected: Vector[Any] +) -> None: + new_value = pickle.loads(pickled_value) + assert new_value == expected diff --git a/tests/unit/waveform/test_analog_waveform.py b/tests/unit/waveform/test_analog_waveform.py index 745c173a..c67c1dcc 100644 --- a/tests/unit/waveform/test_analog_waveform.py +++ b/tests/unit/waveform/test_analog_waveform.py @@ -2003,3 +2003,25 @@ def test___waveform___pickle___references_public_modules() -> None: assert b"nitypes.waveform._numeric" not in value_bytes assert b"nitypes.waveform._timing" not in value_bytes assert b"nitypes.waveform._scaling" not in value_bytes + + +@pytest.mark.parametrize( + "pickled_value, expected", + [ + # nitypes 1.0.0 + ( + b"\x80\x04\x95n\x02\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x07getattr\x94\x93\x94\x8c\x10nitypes.waveform\x94\x8c\x0eAnalogWaveform\x94\x93\x94\x8c\t_unpickle\x94\x86\x94R\x94K\x03\x8c\x05numpy\x94\x8c\x05dtype\x94\x93\x94\x8c\x02f8\x94\x89\x88\x87\x94R\x94(K\x03\x8c\x01<\x94NNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00t\x94b\x86\x94}\x94(\x8c\x08raw_data\x94\x8c\x16numpy._core.multiarray\x94\x8c\x0c_reconstruct\x94\x93\x94h\t\x8c\x07ndarray\x94\x93\x94K\x00\x85\x94C\x01b\x94\x87\x94R\x94(K\x01K\x03\x85\x94h\x0e\x89C\x18\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x08@\x94t\x94b\x8c\x13extended_properties\x94h\x03\x8c\x1aExtendedPropertyDictionary\x94\x93\x94)\x81\x94N}\x94\x8c\x0b_properties\x94}\x94(\x8c\x0eNI_ChannelName\x94\x8c\x08Dev1/ai0\x94\x8c\x12NI_UnitDescription\x94\x8c\x05Volts\x94us\x86\x94b\x8c\x18copy_extended_properties\x94\x89\x8c\x06timing\x94h\x02h\x03\x8c\x06Timing\x94\x93\x94h\x06\x86\x94R\x94(h\x03\x8c\x12SampleIntervalMode\x94\x93\x94K\x01\x85\x94R\x94NN\x8c\x08datetime\x94\x8c\ttimedelta\x94\x93\x94K\x00K\x00M\xe8\x03\x87\x94R\x94Nt\x94}\x94\x86\x94R\x94\x8c\nscale_mode\x94h\x03\x8c\x0fLinearScaleMode\x94\x93\x94)\x81\x94N}\x94(\x8c\x05_gain\x94G@\x00\x00\x00\x00\x00\x00\x00\x8c\x07_offset\x94G?\xf0\x00\x00\x00\x00\x00\x00u\x86\x94bu\x86\x94R\x94.", + AnalogWaveform( + raw_data=np.array([1, 2, 3], np.float64), + extended_properties={"NI_ChannelName": "Dev1/ai0", "NI_UnitDescription": "Volts"}, + timing=Timing.create_with_regular_interval(dt.timedelta(milliseconds=1)), + scale_mode=LinearScaleMode(2.0, 1.0), + ), + ), + ], +) +def test___pickled_value___unpickle___is_compatible( + pickled_value: bytes, expected: AnalogWaveform[Any] +) -> None: + new_value = pickle.loads(pickled_value) + assert new_value == expected diff --git a/tests/unit/waveform/test_complex_waveform.py b/tests/unit/waveform/test_complex_waveform.py index b0b80387..b82b6d47 100644 --- a/tests/unit/waveform/test_complex_waveform.py +++ b/tests/unit/waveform/test_complex_waveform.py @@ -649,3 +649,25 @@ def test___waveform___pickle___references_public_modules() -> None: assert b"nitypes.waveform._numeric" not in value_bytes assert b"nitypes.waveform._timing" not in value_bytes assert b"nitypes.waveform._scaling" not in value_bytes + + +@pytest.mark.parametrize( + "pickled_value, expected", + [ + # nitypes 1.0.0 + ( + b"\x80\x04\x95\x88\x02\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x07getattr\x94\x93\x94\x8c\x10nitypes.waveform\x94\x8c\x0fComplexWaveform\x94\x93\x94\x8c\t_unpickle\x94\x86\x94R\x94K\x03\x8c\x05numpy\x94\x8c\x05dtype\x94\x93\x94\x8c\x03c16\x94\x89\x88\x87\x94R\x94(K\x03\x8c\x01<\x94NNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00t\x94b\x86\x94}\x94(\x8c\x08raw_data\x94\x8c\x16numpy._core.multiarray\x94\x8c\x0c_reconstruct\x94\x93\x94h\t\x8c\x07ndarray\x94\x93\x94K\x00\x85\x94C\x01b\x94\x87\x94R\x94(K\x01K\x03\x85\x94h\x0e\x89C0\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x00\x00\x94t\x94b\x8c\x13extended_properties\x94h\x03\x8c\x1aExtendedPropertyDictionary\x94\x93\x94)\x81\x94N}\x94\x8c\x0b_properties\x94}\x94(\x8c\x0eNI_ChannelName\x94\x8c\x08Dev1/ai0\x94\x8c\x12NI_UnitDescription\x94\x8c\x05Volts\x94us\x86\x94b\x8c\x18copy_extended_properties\x94\x89\x8c\x06timing\x94h\x02h\x03\x8c\x06Timing\x94\x93\x94h\x06\x86\x94R\x94(h\x03\x8c\x12SampleIntervalMode\x94\x93\x94K\x01\x85\x94R\x94NN\x8c\x08datetime\x94\x8c\ttimedelta\x94\x93\x94K\x00K\x00M\xe8\x03\x87\x94R\x94Nt\x94}\x94\x86\x94R\x94\x8c\nscale_mode\x94h\x03\x8c\x0fLinearScaleMode\x94\x93\x94)\x81\x94N}\x94(\x8c\x05_gain\x94G@\x00\x00\x00\x00\x00\x00\x00\x8c\x07_offset\x94G?\xf0\x00\x00\x00\x00\x00\x00u\x86\x94bu\x86\x94R\x94.", + ComplexWaveform( + raw_data=np.array([1, 2, 3], np.complex128), + extended_properties={"NI_ChannelName": "Dev1/ai0", "NI_UnitDescription": "Volts"}, + timing=Timing.create_with_regular_interval(dt.timedelta(milliseconds=1)), + scale_mode=LinearScaleMode(2.0, 1.0), + ), + ), + ], +) +def test___pickled_value___unpickle___is_compatible( + pickled_value: bytes, expected: ComplexWaveform[Any] +) -> None: + new_value = pickle.loads(pickled_value) + assert new_value == expected diff --git a/tests/unit/waveform/test_digital_waveform.py b/tests/unit/waveform/test_digital_waveform.py index 70932c5f..d2fe884b 100644 --- a/tests/unit/waveform/test_digital_waveform.py +++ b/tests/unit/waveform/test_digital_waveform.py @@ -1965,6 +1965,40 @@ def test___waveform___pickle___references_public_modules() -> None: assert b"nitypes.waveform._timing" not in value_bytes +@pytest.mark.parametrize( + "pickled_value, expected", + [ + # nitypes 1.0.0 + pytest.param( + b"\x80\x04\x95\x08\x02\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x07getattr\x94\x93\x94\x8c\x10nitypes.waveform\x94\x8c\x0fDigitalWaveform\x94\x93\x94\x8c\t_unpickle\x94\x86\x94R\x94K\x03K\x01\x8c\x05numpy\x94\x8c\x05dtype\x94\x93\x94\x8c\x02b1\x94\x89\x88\x87\x94R\x94(K\x03\x8c\x01|\x94NNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00t\x94b\x87\x94}\x94(\x8c\x04data\x94\x8c\x16numpy._core.multiarray\x94\x8c\x0c_reconstruct\x94\x93\x94h\t\x8c\x07ndarray\x94\x93\x94K\x00\x85\x94C\x01b\x94\x87\x94R\x94(K\x01K\x03K\x01\x86\x94h\x0e\x89C\x03\x01\x01\x01\x94t\x94b\x8c\x13extended_properties\x94h\x03\x8c\x1aExtendedPropertyDictionary\x94\x93\x94)\x81\x94N}\x94\x8c\x0b_properties\x94}\x94(\x8c\x0eNI_ChannelName\x94\x8c\x08Dev1/ai0\x94\x8c\x12NI_UnitDescription\x94\x8c\x05Volts\x94us\x86\x94b\x8c\x18copy_extended_properties\x94\x89\x8c\x06timing\x94h\x02h\x03\x8c\x06Timing\x94\x93\x94h\x06\x86\x94R\x94(h\x03\x8c\x12SampleIntervalMode\x94\x93\x94K\x01\x85\x94R\x94NN\x8c\x08datetime\x94\x8c\ttimedelta\x94\x93\x94K\x00K\x00M\xe8\x03\x87\x94R\x94Nt\x94}\x94\x86\x94R\x94u\x86\x94R\x94.", + DigitalWaveform( + data=np.array([1, 2, 3], _np_bool), + extended_properties={"NI_ChannelName": "Dev1/ai0", "NI_UnitDescription": "Volts"}, + timing=Timing.create_with_regular_interval(dt.timedelta(milliseconds=1)), + ), + marks=pytest.mark.xfail( + raises=AttributeError, + reason="https://github.com/ni/nitypes-python/issues/234 - ExtendedPropertyDictionary._on_key_changed breaks pickle compatibility", + ), + ), + # nitypes 1.0.1 + ( + b"\x80\x04\x95\xf4\x01\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x07getattr\x94\x93\x94\x8c\x10nitypes.waveform\x94\x8c\x0fDigitalWaveform\x94\x93\x94\x8c\t_unpickle\x94\x86\x94R\x94K\x03K\x01\x8c\x05numpy\x94\x8c\x05dtype\x94\x93\x94\x8c\x02b1\x94\x89\x88\x87\x94R\x94(K\x03\x8c\x01|\x94NNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00t\x94b\x87\x94}\x94(\x8c\x04data\x94\x8c\x16numpy._core.multiarray\x94\x8c\x0c_reconstruct\x94\x93\x94h\t\x8c\x07ndarray\x94\x93\x94K\x00\x85\x94C\x01b\x94\x87\x94R\x94(K\x01K\x03K\x01\x86\x94h\x0e\x89C\x03\x01\x01\x01\x94t\x94b\x8c\x13extended_properties\x94h\x03\x8c\x1aExtendedPropertyDictionary\x94\x93\x94}\x94(\x8c\x0eNI_ChannelName\x94\x8c\x08Dev1/ai0\x94\x8c\x12NI_UnitDescription\x94\x8c\x05Volts\x94u\x85\x94R\x94\x8c\x18copy_extended_properties\x94\x89\x8c\x06timing\x94h\x02h\x03\x8c\x06Timing\x94\x93\x94h\x06\x86\x94R\x94(h\x03\x8c\x12SampleIntervalMode\x94\x93\x94K\x01\x85\x94R\x94NN\x8c\x08datetime\x94\x8c\ttimedelta\x94\x93\x94K\x00K\x00M\xe8\x03\x87\x94R\x94Nt\x94}\x94\x86\x94R\x94u\x86\x94R\x94.", + DigitalWaveform( + data=np.array([1, 2, 3], _np_bool), + extended_properties={"NI_ChannelName": "Dev1/ai0", "NI_UnitDescription": "Volts"}, + timing=Timing.create_with_regular_interval(dt.timedelta(milliseconds=1)), + ), + ), + ], +) +def test___pickled_value___unpickle___is_compatible( + pickled_value: bytes, expected: DigitalWaveform[Any] +) -> None: + new_value = pickle.loads(pickled_value) + assert new_value == expected + + def test___waveform_with_extended_properties___pickle_unpickle___valid_on_key_changed() -> None: value = DigitalWaveform( data=np.array([1, 2, 3], _np_bool), diff --git a/tests/unit/waveform/test_digital_waveform_signal.py b/tests/unit/waveform/test_digital_waveform_signal.py index 08390a30..e6174382 100644 --- a/tests/unit/waveform/test_digital_waveform_signal.py +++ b/tests/unit/waveform/test_digital_waveform_signal.py @@ -367,3 +367,33 @@ def test___waveform___pickle___references_public_modules() -> None: assert b"nitypes.waveform" in value_bytes assert b"nitypes.waveform._digital" not in value_bytes + + +@pytest.mark.parametrize( + "pickled_value, expected", + [ + # nitypes 1.0.0 + pytest.param( + b"\x80\x04\x95\xfa\x01\x00\x00\x00\x00\x00\x00\x8c\x10nitypes.waveform\x94\x8c\x15DigitalWaveformSignal\x94\x93\x94\x8c\x08builtins\x94\x8c\x07getattr\x94\x93\x94h\x00\x8c\x0fDigitalWaveform\x94\x93\x94\x8c\t_unpickle\x94\x86\x94R\x94K\x03K\x02\x8c\x05numpy\x94\x8c\x05dtype\x94\x93\x94\x8c\x02u1\x94\x89\x88\x87\x94R\x94(K\x03\x8c\x01|\x94NNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00t\x94b\x87\x94}\x94(\x8c\x04data\x94\x8c\x16numpy._core.multiarray\x94\x8c\x0c_reconstruct\x94\x93\x94h\x0b\x8c\x07ndarray\x94\x93\x94K\x00\x85\x94C\x01b\x94\x87\x94R\x94(K\x01K\x03K\x02\x86\x94h\x10\x89C\x06\x00\x00\x00\x00\x00\x00\x94t\x94b\x8c\x13extended_properties\x94h\x00\x8c\x1aExtendedPropertyDictionary\x94\x93\x94)\x81\x94N}\x94\x8c\x0b_properties\x94}\x94\x8c\x0cNI_LineNames\x94\x8c\x18port0/line1, port0/line0\x94ss\x86\x94b\x8c\x18copy_extended_properties\x94\x89\x8c\x06timing\x94h\x05h\x00\x8c\x06Timing\x94\x93\x94h\x08\x86\x94R\x94(h\x00\x8c\x12SampleIntervalMode\x94\x93\x94K\x00\x85\x94R\x94NNNNt\x94}\x94\x86\x94R\x94u\x86\x94R\x94K\x01\x86\x94R\x94.", + DigitalWaveform( + 3, 2, extended_properties={"NI_LineNames": "port0/line1, port0/line0"} + ).signals[1], + marks=pytest.mark.xfail( + raises=AttributeError, + reason="https://github.com/ni/nitypes-python/issues/234 - ExtendedPropertyDictionary._on_key_changed breaks pickle compatibility", + ), + ), + # nitypes 1.0.1 + ( + b"\x80\x04\x95\xe8\x01\x00\x00\x00\x00\x00\x00\x8c\x10nitypes.waveform\x94\x8c\x15DigitalWaveformSignal\x94\x93\x94\x8c\x08builtins\x94\x8c\x07getattr\x94\x93\x94h\x00\x8c\x0fDigitalWaveform\x94\x93\x94\x8c\t_unpickle\x94\x86\x94R\x94K\x03K\x02\x8c\x05numpy\x94\x8c\x05dtype\x94\x93\x94\x8c\x02u1\x94\x89\x88\x87\x94R\x94(K\x03\x8c\x01|\x94NNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00t\x94b\x87\x94}\x94(\x8c\x04data\x94\x8c\x16numpy._core.multiarray\x94\x8c\x0c_reconstruct\x94\x93\x94h\x0b\x8c\x07ndarray\x94\x93\x94K\x00\x85\x94C\x01b\x94\x87\x94R\x94(K\x01K\x03K\x02\x86\x94h\x10\x89C\x06\x00\x00\x00\x00\x00\x00\x94t\x94b\x8c\x13extended_properties\x94h\x00\x8c\x1aExtendedPropertyDictionary\x94\x93\x94}\x94\x8c\x0cNI_LineNames\x94\x8c\x18port0/line1, port0/line0\x94s\x85\x94R\x94\x8c\x18copy_extended_properties\x94\x89\x8c\x06timing\x94h\x05h\x00\x8c\x06Timing\x94\x93\x94h\x08\x86\x94R\x94(h\x00\x8c\x12SampleIntervalMode\x94\x93\x94K\x00\x85\x94R\x94NNNNt\x94}\x94\x86\x94R\x94u\x86\x94R\x94K\x01K\x00\x87\x94R\x94.", + DigitalWaveform( + 3, 2, extended_properties={"NI_LineNames": "port0/line1, port0/line0"} + ).signals[1], + ), + ], +) +def test___pickled_value___unpickle___is_compatible( + pickled_value: bytes, expected: DigitalWaveformSignal[Any] +) -> None: + new_value = pickle.loads(pickled_value) + assert new_value == expected diff --git a/tests/unit/waveform/test_extended_property_dictionary.py b/tests/unit/waveform/test_extended_property_dictionary.py new file mode 100644 index 00000000..82304292 --- /dev/null +++ b/tests/unit/waveform/test_extended_property_dictionary.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +import pickle + +import pytest + +from nitypes.waveform import ExtendedPropertyDictionary + + +@pytest.mark.parametrize( + "pickled_value, expected", + [ + # nitypes 1.0.0 + ( + b"\x80\x04\x95\x88\x00\x00\x00\x00\x00\x00\x00\x8c\x10nitypes.waveform\x94\x8c\x1aExtendedPropertyDictionary\x94\x93\x94)\x81\x94N}\x94\x8c\x0b_properties\x94}\x94(\x8c\x0eNI_ChannelName\x94\x8c\x08Dev1/ai0\x94\x8c\x12NI_UnitDescription\x94\x8c\x05Volts\x94us\x86\x94b.", + ExtendedPropertyDictionary( + {"NI_ChannelName": "Dev1/ai0", "NI_UnitDescription": "Volts"} + ), + ), + # nitypes 1.0.1 + ( + b"\x80\x04\x95t\x00\x00\x00\x00\x00\x00\x00\x8c\x10nitypes.waveform\x94\x8c\x1aExtendedPropertyDictionary\x94\x93\x94}\x94(\x8c\x0eNI_ChannelName\x94\x8c\x08Dev1/ai0\x94\x8c\x12NI_UnitDescription\x94\x8c\x05Volts\x94u\x85\x94R\x94.", + ExtendedPropertyDictionary( + {"NI_ChannelName": "Dev1/ai0", "NI_UnitDescription": "Volts"} + ), + ), + ], +) +def test___pickled_value___unpickle___is_compatible( + pickled_value: bytes, expected: ExtendedPropertyDictionary +) -> None: + new_value = pickle.loads(pickled_value) + assert new_value == expected diff --git a/tests/unit/waveform/test_spectrum.py b/tests/unit/waveform/test_spectrum.py index aa6f3965..ad97576a 100644 --- a/tests/unit/waveform/test_spectrum.py +++ b/tests/unit/waveform/test_spectrum.py @@ -1343,3 +1343,25 @@ def test___spectrum___pickle___references_public_modules() -> None: assert b"nitypes.waveform" in value_bytes assert b"nitypes.waveform._extended_properties" not in value_bytes assert b"nitypes.waveform._spectrum" not in value_bytes + + +@pytest.mark.parametrize( + "pickled_value, expected", + [ + # nitypes 1.0.0 + ( + b"\x80\x04\x95\xdf\x01\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x07getattr\x94\x93\x94\x8c\x10nitypes.waveform\x94\x8c\x08Spectrum\x94\x93\x94\x8c\t_unpickle\x94\x86\x94R\x94K\x03\x8c\x05numpy\x94\x8c\x05dtype\x94\x93\x94\x8c\x02f8\x94\x89\x88\x87\x94R\x94(K\x03\x8c\x01<\x94NNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00t\x94b\x86\x94}\x94(\x8c\x04data\x94\x8c\x16numpy._core.multiarray\x94\x8c\x0c_reconstruct\x94\x93\x94h\t\x8c\x07ndarray\x94\x93\x94K\x00\x85\x94C\x01b\x94\x87\x94R\x94(K\x01K\x03\x85\x94h\x0e\x89C\x18\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x08@\x94t\x94b\x8c\x0fstart_frequency\x94G@^\xdd/\x1a\x9f\xbew\x8c\x13frequency_increment\x94G?\xb9\x99\x99\x99\x99\x99\x9a\x8c\x13extended_properties\x94h\x03\x8c\x1aExtendedPropertyDictionary\x94\x93\x94)\x81\x94N}\x94\x8c\x0b_properties\x94}\x94(\x8c\x0eNI_ChannelName\x94\x8c\x08Dev1/ai0\x94\x8c\x12NI_UnitDescription\x94\x8c\x05Volts\x94us\x86\x94b\x8c\x18copy_extended_properties\x94\x89u\x86\x94R\x94.", + Spectrum( + data=np.array([1, 2, 3], np.float64), + start_frequency=123.456, + frequency_increment=0.1, + extended_properties={"NI_ChannelName": "Dev1/ai0", "NI_UnitDescription": "Volts"}, + ), + ), + ], +) +def test___pickled_value___unpickle___is_compatible( + pickled_value: bytes, expected: Spectrum[Any] +) -> None: + new_value = pickle.loads(pickled_value) + assert new_value == expected diff --git a/tests/unit/xy_data/test_xy_data.py b/tests/unit/xy_data/test_xy_data.py index 246a7da1..8b0441f7 100644 --- a/tests/unit/xy_data/test_xy_data.py +++ b/tests/unit/xy_data/test_xy_data.py @@ -476,6 +476,23 @@ def test___xy_data___pickle___references_public_modules() -> None: assert b"nitypes.xy_data._xy_data" not in value_bytes +@pytest.mark.parametrize( + "pickled_value, expected", + [ + # nitypes 1.1.0 + ( + b"\x80\x04\x95\xb7\x01\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x07getattr\x94\x93\x94\x8c\x0fnitypes.xy_data\x94\x8c\x06XYData\x94\x93\x94\x8c\t_unpickle\x94\x86\x94R\x94\x8c\x16numpy._core.multiarray\x94\x8c\x0c_reconstruct\x94\x93\x94\x8c\x05numpy\x94\x8c\x07ndarray\x94\x93\x94K\x00\x85\x94C\x01b\x94\x87\x94R\x94(K\x01K\x02\x85\x94h\x0c\x8c\x05dtype\x94\x93\x94\x8c\x02f8\x94\x89\x88\x87\x94R\x94(K\x03\x8c\x01<\x94NNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00t\x94b\x89C\x10\x00\x00\x00\x00\x00\x004@\x9a\x99\x99\x99\x99\x194@\x94t\x94bh\x0bh\x0eK\x00\x85\x94h\x10\x87\x94R\x94(K\x01K\x02\x85\x94h\x18\x89C\x10\xcd\xcc\xcc\xcc\xccL4@ffffff4@\x94t\x94b\x86\x94}\x94(\x8c\x13extended_properties\x94\x8c\x10nitypes.waveform\x94\x8c\x1aExtendedPropertyDictionary\x94\x93\x94}\x94(\x8c\x14NI_UnitDescription_X\x94\x8c\x01C\x94\x8c\x14NI_UnitDescription_Y\x94\x8c\x01D\x94u\x85\x94R\x94\x8c\x18copy_extended_properties\x94\x89u\x86\x94R\x94.", + XYData.from_arrays_1d([20.0, 20.1], [20.3, 20.4], np.float64, x_units="C", y_units="D"), + ), + ], +) +def test___pickled_value___unpickle___is_compatible( + pickled_value: bytes, expected: XYData[Any] +) -> None: + new_value = pickle.loads(pickled_value) + assert new_value == expected + + def test___various_units_values___change_units___updates_units_correctly() -> None: data = XYData.from_arrays_1d([1], [2], np.int32) data.x_units = "volts"