Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
9 changes: 7 additions & 2 deletions p4pillon/server/raw.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import logging
from abc import ABC

from p4p._p4p import SharedPV as _RawSharedPV
from p4p.server.raw import SharedPV as _SharedPV

_log = logging.getLogger(__name__)
Expand Down Expand Up @@ -154,7 +155,9 @@ def open(self, value, nt=None, wrap=None, unwrap=None, **kwargs):
else:
open_fn(V)

_SharedPV.open(self, V)
# Call the C-extension base directly, bypassing p4p.server.raw.SharedPV.open(),
# which would wrap() V a second time (V is already wrapped above).
_RawSharedPV.open(self, V)

def post(self, value, **kwargs):
"""Provide an update to the Value of this PV.
Expand All @@ -180,7 +183,9 @@ def post(self, value, **kwargs):
else:
post_fn(self, V)

_SharedPV.post(self, V)
# Call the C-extension base directly, bypassing p4p.server.raw.SharedPV.post(),
# which would wrap() V a second time (V is already wrapped above).
_RawSharedPV.post(self, V)

def close(self, destroy=False):
"""Close PV, disconnecting any clients.
Expand Down
22 changes: 21 additions & 1 deletion tests/unit/server/test_sharedpv_asyncio.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from p4p.nt import NTScalar
import numpy
from p4p.nt import NTNDArray, NTScalar

from p4pillon.server.asyncio import Handler, SharedPV

Expand Down Expand Up @@ -51,3 +52,22 @@ def teardown_method(self, _method):
self.pv.close()
del self.handler
del self.pv


class TestNoDoubleWrapOfInitialValue:
"""Regression tests for `SharedPV.open()`/`.post()` each wrapping `value`
via `nt.wrap()` themselves before delegating to p4p's own (already
wrapping) `SharedPV.open()`/`.post()`, which wraps a second time. Harmless
for `NTScalar`, whose `wrap()` tolerates being fed an already-wrapped
`Value` -- but `NTNDArray.wrap()` assumes a raw `numpy.ndarray` and raises
when handed a `Value` on the second pass.
"""

def test_open_with_ntndarray_does_not_double_wrap(self):
pv = SharedPV(nt=NTNDArray(), initial=numpy.zeros((4, 4)))
assert numpy.array_equal(numpy.asarray(pv.current()).flatten(), numpy.zeros(16))

def test_post_with_ntndarray_does_not_double_wrap(self):
pv = SharedPV(nt=NTNDArray(), initial=numpy.zeros((4, 4)))
pv.post(numpy.ones((4, 4)))
assert numpy.array_equal(numpy.asarray(pv.current()).flatten(), numpy.ones(16))
22 changes: 21 additions & 1 deletion tests/unit/server/test_sharedpv_thread.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from p4p.nt import NTScalar
import numpy
from p4p.nt import NTNDArray, NTScalar

from p4pillon.server.thread import Handler, SharedPV

Expand Down Expand Up @@ -51,3 +52,22 @@ def teardown_method(self, _method):
self.pv.close()
del self.handler
del self.pv


class TestNoDoubleWrapOfInitialValue:
"""Regression tests for `SharedPV.open()`/`.post()` each wrapping `value`
via `nt.wrap()` themselves before delegating to p4p's own (already
wrapping) `SharedPV.open()`/`.post()`, which wraps a second time. Harmless
for `NTScalar`, whose `wrap()` tolerates being fed an already-wrapped
`Value` -- but `NTNDArray.wrap()` assumes a raw `numpy.ndarray` and raises
when handed a `Value` on the second pass.
"""

def test_open_with_ntndarray_does_not_double_wrap(self):
pv = SharedPV(nt=NTNDArray(), initial=numpy.zeros((4, 4)))
assert numpy.array_equal(numpy.asarray(pv.current()).flatten(), numpy.zeros(16))

def test_post_with_ntndarray_does_not_double_wrap(self):
pv = SharedPV(nt=NTNDArray(), initial=numpy.zeros((4, 4)))
pv.post(numpy.ones((4, 4)))
assert numpy.array_equal(numpy.asarray(pv.current()).flatten(), numpy.ones(16))
Loading