def wait_for_update(self, test_uid, remote_state_dict, timeout_s): """Long-poll RPC that blocks until the test state has an update. Events that trigger an update: Test Status Changes (ie, transition from WAITING_FOR_START to RUNNING). Phase Start/Finish. Measurement is set/updated. Attachment is attached. Log line is produced (via TestApi.logger). Note that plug state changes do NOT trigger an update here, use wait_for_plug_update() to get plug state change events. Args: test_uid: Test UID for which to wait on an update. remote_state_dict: Current RemoteState that we have for the test, as a dict, with phases and log_records swapped out for counts instead of the actual records themselves. timeout_s: Number of seconds to wait for an update before giving up. Returns: Updated RemoteState, as per get_test_state, except args are taken from remote_state_dict rather than passed in individually (because we care about more stuff here). In the event of a timeout, returns None. Raises: UnrecognizedTestUidError: If the test_uid is not recognized. UpdateTimeout: If there was no new information before the timeout expired, as differentiated from a None return value, which indicates the requested test is not being Execute()'ed on the remote side (but we previously thought it was). """ _LOG.debug('RPC:wait_for_update(timeout_s=%s)', timeout_s) state = openhtf.Test.from_uid(test_uid).state # Handle the case in which the test is not running. if state is None: if remote_state_dict: # Remote end expected the test to be running but it's not. This is all # the information we need to return immediately. return # Remote end expected that the test was not running, so wait for it to # start. state = timeouts.loop_until_timeout_or_not_none( timeout_s, lambda: openhtf.Test.from_uid(test_uid).state, sleep_s=.1) if state is None: raise UpdateTimeout( "No test started Execute()'ing before timeout", timeout_s) _LOG.debug( 'RPC:wait_for_update() -> short-circuited wait (local was blank)' ) return self._serialize_state_dict(state._asdict()) state_dict, update_event = state.asdict_with_event() state_dict_summary = self._summary_for_state_dict(state_dict) # Deserialize the RemoteState fields for comparison. remote_state_dict = remote_state_dict and { 'status': test_state.TestState.Status[remote_state_dict['status']], 'test_record': remote_state_dict['test_record'], 'running_phase_state': remote_state_dict['running_phase_state'], } if state_dict_summary != remote_state_dict: if not remote_state_dict: _LOG.debug( 'RPC:wait_for_update() -> short-circuited wait (remote was blank)' ) elif _LOG.isEnabledFor(logging.DEBUG): log_msg = [ 'RPC:wait_for_update() -> short-circuited wait, diff:' ] log_msg.extend( data.pprint_diff(remote_state_dict, state_dict_summary, 'remote_state', 'local_state')) _LOG.debug('\n'.join(log_msg)) # We already have new info, serialize the new state and send it, # skipping any phases/logs that we already know about remotely. return self._serialize_state_dict( state_dict, remote_state_dict and remote_state_dict['test_record']) # If we get here, then the remote side is already up-to-date, so we wait # for there to be new information available. We rely on the TestState # object itself to notify us when this is the case. if not update_event.wait(timeout_s): _LOG.debug('RPC:wait_for_update() -> timeout after %s seconds', timeout_s) raise UpdateTimeout('No new information before timeout.', timeout_s) _LOG.debug('RPC:wait_for_update() -> change after wait') # Grab a fresh copy of the state and return the new info. state = openhtf.Test.from_uid(test_uid).state return state and self._serialize_state_dict( state._asdict(), remote_state_dict['test_record'])
def wait_for_update(self, test_uid, remote_state_dict, timeout_s): """Long-poll RPC that blocks until there is new information available. Events that trigger an update: Test Status Changes (ie, transition from WAITING_FOR_START to RUNNING). Phase Start/Finish. Measurement is set/updated. Attachment is attached. Log line is produced (via TestApi.logger). Note that plug state changes do NOT trigger an update here, use wait_for_plug() to get plug state change events. Args: test_uid: Test UID for which to wait on an update. remote_state_dict: Current RemoteState that we have for the test, as a dict, with phases and log_records swapped out for counts instead of the actual records themselves. timeout_s: Number of seconds to wait for an update before giving up. Returns: Updated RemoteState, as per get_test_state, except args are taken from remote_state_dict rather than passed in individually (because we care about more stuff here). In the event of a timeout, returns None. Raises: UnrecognizedTestUidError: If the test_uid is not recognized. UpdateTimeout: If there was no new information before the timeout expired, as differentiated from a None return value, which indicates the requested test is not being Execute()'ed on the remote side (but we previously thought it was). """ _LOG.debug('RPC:wait_for_update(timeout_s=%s)', timeout_s) state = openhtf.Test.state_by_uid(test_uid) if state is None: if remote_state_dict: # Remote end expects there to be a test running but there isn't, this # is all the information we need to return immediately. return # Remote end already thinks the test isn't Execute()'ing, so wait for it. state = timeouts.LoopUntilTimeoutOrNotNone( timeout_s, lambda: openhtf.Test.state_by_uid(test_uid), sleep_s=.1) if not state: raise UpdateTimeout( "No test started Execute()'ing before timeout", timeout_s) _LOG.debug( 'RPC:wait_for_update() -> short-circuited wait (local was blank)') return self._serialize_state_dict(state._asdict()) state_dict, update_event = state.asdict_with_event() state_dict_summary = self._summary_for_state_dict(state_dict) # Deserialize the RemoteState fields for comparison. remote_state_dict = remote_state_dict and { 'status': test_state.TestState.Status[remote_state_dict['status']], 'test_record': pickle.loads(remote_state_dict['test_record'].data), 'running_phase_state': remote_state_dict['running_phase_state'], } if state_dict_summary != remote_state_dict: if not remote_state_dict: _LOG.debug( 'RPC:wait_for_update() -> short-circuited wait (remote was blank)') elif _LOG.isEnabledFor(logging.DEBUG): log_msg = ['RPC:wait_for_update() -> short-circuited wait, diff:'] log_msg.extend( data.pprint_diff(remote_state_dict, state_dict_summary, 'remote_state', 'local_state')) _LOG.debug('\n'.join(log_msg)) # We already have new info, serialize the new state and send it, # skipping any phases/logs that we already know about remotely. return self._serialize_state_dict( state_dict, remote_state_dict and remote_state_dict['test_record']) # If we get here, then the remote side is already up-to-date, so we wait # for there to be new information available. We rely on the TestState # object itself to notify us when this is the case. if not update_event.wait(timeout_s): _LOG.debug('RPC:wait_for_update() -> timeout after %s seconds', timeout_s) raise UpdateTimeout('No new information before timeout.', timeout_s) _LOG.debug('RPC:wait_for_update() -> change after wait') # Grab a fresh copy of the state and return the new info. state = openhtf.Test.state_by_uid(test_uid) return state and self._serialize_state_dict( state._asdict(), remote_state_dict['test_record'])