pcdshub / pcdsdaq

Utilities for using the DAQ's pydaq, pycdb, and pyami libraries in conjunction with Bluesky
https://pcdshub.github.io/pcdsdaq
Other
0 stars 9 forks source link

Fix failing test_configure in lcls2 interface #117

Open ZLLentz opened 1 year ago

ZLLentz commented 1 year ago

One of the lcls2 tests is failing and it looks like a real issue. This interface is not deployed in any hutches yet.

``` =================================== FAILURES =================================== ________________________________ test_configure ________________________________ daq_lcls2 = DaqLCLS2(, name=daq) def test_configure(daq_lcls2: DaqLCLS2): """ Configure must have the following behavior: - kwargs end up in cfg signals (spot check 1 or 2) - Returns (old_cfg, new_cfg) - Configure transition caused if needed from conn/conf states - Conf needed if recording gui clicked, or critical kwargs changed, or if never done, or if someone else configured - From the conf state, we unconf before confing - Configure transition not caused if not needed - Error if we're not in conn/conf states and a transition is needed - Controls arg processed with no errors (except bad user ValueError) - record doesn't require a configure transition, but it does require no open run """ logger.debug('test_configure') # The first configure should cause a transition # Let's start in connected and check the basic stuff daq_lcls2._control.sim_set_states( transition='connect', state='connected', ) daq_lcls2._get_status_for(state=['connected']).wait(timeout=1) prev_tst = daq_lcls2.read_configuration() prev_cfg, post_cfg = daq_lcls2.configure(events=100, detname='dat') assert daq_lcls2.events_cfg.get() == 100 assert daq_lcls2.detname_cfg.get() == 'dat' post_tst = daq_lcls2.read_configuration() assert (prev_cfg, post_cfg) == (prev_tst, post_tst) # Changing controls should make us reconfigure st_conn = daq_lcls2._get_status_for(state=['connected'], check_now=False) st_conf = daq_lcls2._get_status_for(state=['configured'], check_now=False) daq_lcls2.configure(controls=(Signal(name='sig'),)) st_conn.wait(timeout=1) st_conf.wait(timeout=1) # Changing events should not make us reconfigure st_any = daq_lcls2._get_status_for(check_now=False) daq_lcls2.configure(events=1000) with pytest.raises(WaitTimeoutError): st_any.wait(1) st_any.set_finished() # Any out of process configuration should make us reconfigure daq_lcls2._control.setState('connected', {}) sig_wait_value(daq_lcls2.state_sig, daq_lcls2.state_enum.connected) daq_lcls2._control.setState('configured', {}) sig_wait_value(daq_lcls2.state_sig, daq_lcls2.state_enum.configured) st_conn = daq_lcls2._get_status_for(state=['connected'], check_now=False) st_conf = daq_lcls2._get_status_for(state=['configured'], check_now=False) assert ( daq_lcls2.configures_seen_sig.get() > daq_lcls2.configures_requested_sig.get() ) daq_lcls2.configure() st_conn.wait(timeout=1) st_conf.wait(timeout=1) # Configure should error if transition needed from most of the states bad_states = daq_lcls2.state_enum.exclude(['connected', 'configured']) for state in bad_states: logger.debug('testing %s', state) daq_lcls2.state_sig.put(state) with pytest.raises(RuntimeError): daq_lcls2.configure(detname=state.name) # Let's set up a controls arg and make sure it at least doesn't error # Hard to check the DAQ side without making a very sophisticated sim daq_lcls2._control.setState('connected', {}) daq_lcls2._get_status_for(state=['connected']).wait(timeout=1) daq_lcls2._last_config = {} daq_lcls2.configure(controls=( Signal(name='sig'), SoftPositioner(init_pos=0, name='pos'), ('const', 3), )) # Again, but with at least one example of a bad controls arg daq_lcls2._control.setState('connected', {}) daq_lcls2._get_status_for(state=['connected']).wait(timeout=1) daq_lcls2._last_config = {} with pytest.raises(ValueError): daq_lcls2.configure(controls=( Signal(name='one_good_thing'), 'some bad stuff here', 0.[232](https://github.com/pcdshub/pcdsdaq/actions/runs/4369116062/jobs/7642540901#step:17:233)323232323, )) daq_lcls2.preconfig(controls=()) # Changing record during an open run should fail. daq_lcls2.configure(record=True) daq_lcls2._control.setState('running', {}) sig_wait_value(daq_lcls2.recording_sig, True) sig_wait_value(daq_lcls2.state_sig, daq_lcls2.state_enum.running) with pytest.raises(RuntimeError): daq_lcls2.configure(record=False) # Unless it's to the same recording state we already have > daq_lcls2.configure(record=True) tests/test_daq_lcls2.py:397: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = DaqLCLS2(, name=daq) events = duration = record = True controls = motors = begin_timeout = begin_sleep = group_mask = detname = scantype = serial_number = alg_name = alg_version = def configure( self, events: int | None | Sentinel = CONFIG_VAL, duration: Real | None | Sentinel = CONFIG_VAL, record: bool | TernaryBool | None | Sentinel = CONFIG_VAL, controls: ControlsArg | None | Sentinel = CONFIG_VAL, motors: ControlsArg | None | Sentinel = CONFIG_VAL, begin_timeout: Real | None | Sentinel = CONFIG_VAL, begin_sleep: Real | None | Sentinel = CONFIG_VAL, group_mask: int | None | Sentinel = CONFIG_VAL, detname: str | None | Sentinel = CONFIG_VAL, scantype: str | None | Sentinel = CONFIG_VAL, serial_number: str | None | Sentinel = CONFIG_VAL, alg_name: str | None | Sentinel = CONFIG_VAL, alg_version: list[int] | None | Sentinel = CONFIG_VAL, ): """ Adjusts the configuration, causing a "configure" transition if needed. A "configure" transition will be caused in the following cases: 1. We are in the "connected" state 2. We are in the "configured" state but an important configuration parameter has been changed. In this case, we will revert to the "connected" state and then return to the "configured" state. 3. We are in the "configured" state but the configure was caused by some other process, for example, the DAQ GUI or a different Python session. In all other states, this will raise a "RuntimeError" if it decides that a "configure" transition is needed. Arguments that are not provided are not changed. Arguments that are passed as "None" will return to their default values. Parameters ---------- events : int or None, optional The number of events to take per step. Incompatible with the "duration" argument. Defaults to "None", and running without configuring events or duration gives us an endless run (that can be terminated manually). Supplying an argument to "events" will reset "duration" to "None". duration : float or None, optional How long to acquire data at each step in seconds. Incompatible with the "events" argument. Defaults to "None", and running without configuring events or duration dives us an endless run (that can be terminated manually). Supplying an argument to "duration" will reset "events" to "None". record : bool or None, optional Whether or not to save data during the DAQ run. Defaults to "None", which means that we'll keep the DAQ's recording state at whatever it is at the start of the run. Changing the DAQ recording state cannot be done during a run, but it will not require a configure transition. controls : list or tuple of valid objects, or None, optional The objects to include per-step in the DAQ data stream. These must implement the "name" attribute and either the "position" attribute or the "get" method to retrieve their current value. To enforce an alternate name, you can pass a tuple instead of an object where the first element of the tuple is the replacement name. The tuple syntax can also be used to send primitive constants to the DAQ if the constant is an int, float, or str. If None or empty, we'll only include the default DAQ step counter, which will always be included. motors : list or dict of signals or positioners, or None, optional Alias of "controls" for backwards compatibility. begin_timeout : float or None, optional How long to wait before marking a begin run as a failure and raising an exception. begin_sleep : float or None, optional How long to wait before starting a run. group_mask : int or None, optional Bitmask that is used by the DAQ. This docstring writer is not sure exactly what it does. The default is all zeroes with a "1" bitshifted left by the platform number. detname : str or None, optional The name associated with the controls data in the DAQ. Defaults to "scan". scantype : str or None, optional Another string associated with the runs produced by this object in the DAQ. Defaults to "scan". serial_number : str or None, optional Another string associated with the runs produced by this object in the DAQ. Defaults to "1[234](https://github.com/pcdshub/pcdsdaq/actions/runs/4369116062/jobs/7642540901#step:17:235)". alg_name : str or None, optional Another string associated with the runs produced by this object in the DAQ. Defaults to "raw". alg_version : list of int, or None, optional The version numbers [major, minor, bugfix] associated with alg_name. Defaults to [1, 0, 0]. Returns ------- (old, new): tuple[dict, dict] The configurations before and after the function was called. This is used internally by bluesky when we include "configure" in a plan. """ logger.debug("DaqLCLS2.configure, passing to super") old, new = super().configure( events=events, duration=duration, record=record, controls=controls, motors=motors, begin_timeout=begin_timeout, begin_sleep=begin_sleep, group_mask=group_mask, detname=detname, scantype=scantype, serial_number=serial_number, alg_name=alg_name, alg_version=alg_version, ) other_proc_configured = ( self.configures_seen_sig.get() != self.configures_requested_sig.get() ) first_configure = self.configures_requested_sig.get() == 0 # Cause a transition if we need to if any(( self._queue_configure_transition, other_proc_configured, first_configure, )): if self.state_sig.get() < self.state_enum.connected: raise RuntimeError('Not ready to configure.') if self.state_sig.get() > self.state_enum.configured: > raise RuntimeError( 'Cannot configure transition during an open run!' ) E RuntimeError: Cannot configure transition during an open run! ```

Originally posted by @ZLLentz in https://github.com/pcdshub/pcdsdaq/issues/115#issuecomment-1462694398