Repository: DiamondLightSource/pythonSoftIOC (language: python)

Issue:
Unable to run waveform.set() before softioc.iocInit()
In a patch from 2019 (CGP-182, 944e0725), a change was made to the waveform 'set' method. As part of this change, it now uses the 'dtype' attribute.

This attribute is only set when the 'init_record' method is called, which is triggered by the softioc.iocInit() call for IOC startup.

Here is my particular use-case:

The module I am working on has its behaviour split into a number of separate 'servers', each with their own EPICS database setup and associated logic. The 'softioc.iocInit()' method is only called by the top-level program, after each of these servers has had a chance to run its own database setup.

The unit tests do not call 'softioc.iocInit()'. They can, for example, allow the servers to run waveform.set() and then check the parameters afterwards.

This has problems after the above bug-fix, as dtype is not set. It seems sensible to me that unit tests should not need to have a running IOC in order to operate. What is your opinion on allowing get() and set() functions to be called before iocInit()? Do you think the above is a good model for unit testing? If it's not, what alternatives should be used?


Discussion / hints:
Hmm.

Calling `.set()` before `iocInit()` has always been somewhat of an afterthought.  You've certainly encountered an interesting issue, as there certainly is special case support for calling `.set()` on Out records here:
https://github.com/dls-controls/pythonSoftIOC/blob/a9d72019dd05838cfa84f4161fe008d81af8bc98/softioc/device.py#L207-L210

If you can come up with a sensible default behaviour to patch `waveform.set()` here https://github.com/dls-controls/pythonSoftIOC/blob/a9d72019dd05838cfa84f4161fe008d81af8bc98/softioc/device.py#L345 that could be a workaround.

One simple possibility would be to default `dtype` to `None`, as `numpy` does appear to accept this as an option.

Have to say I'm not a particular fan of tests that actually don't run up the IOC, but that's probably not on topic here.
It occurs to me that simply adding the line
```python
    dtype = None
```
straight after this line https://github.com/dls-controls/pythonSoftIOC/blob/a9d72019dd05838cfa84f4161fe008d81af8bc98/softioc/device.py#L295 might be a workable fix.  Let me know if this works for you.
That should work, although I haven't tested it beyond my own module
Create a merge request and mark @thomascobb and me to review.  With luck CI will check it as well...

Fix patch:
diff --git a/softioc/device.py b/softioc/device.py
index ebcf7622..7d125345 100644
--- a/softioc/device.py
+++ b/softioc/device.py
@@ -293,6 +293,8 @@ class WaveformBase(ProcessDeviceSupportCore):
     #   NELM    Length of allocated array in number of elements
     #   NORD    Currently reported length of array (0 <= NORD <= NELM)
     _fields_ = ['UDF', 'FTVL', 'BPTR', 'NELM', 'NORD']
+    # Allow set() to be called before init_record:
+    dtype = None
 
     def init_record(self, record):
         self.dtype = DbfCodeToNumpy[record.FTVL]


Test patch:
diff --git a/tests/sim_records.py b/tests/sim_records.py
index 18d86464..726eea97 100644
--- a/tests/sim_records.py
+++ b/tests/sim_records.py
@@ -42,6 +42,8 @@ def update_sin_wf(value):
     sin_wf.set(numpy.sin(
         numpy.linspace(0, 2*numpy.pi*sin_ph.get(), sin_len.get())))
 sin_wf = Waveform('SIN', datatype = float, length = 1024)
+# Check we can update its value before iocInit as per #22
+sin_wf.set([1, 2, 3])
 sin_len = longOut(
     'SINN', 0, 1024, initial_value=1024, on_update=update_sin_wf)
 sin_ph = aOut('SINP', initial_value = 0.0, on_update = update_sin_wf)