def wait_for_plug_update( self, plug_name: Text, remote_state: Dict[Text, Any], timeout_s: Union[int, float]) -> Optional[Dict[Text, Any]]: """Wait for a change in the state of a frontend-aware plug. Args: plug_name: Plug name, e.g. 'openhtf.plugs.user_input.UserInput'. remote_state: The last observed state. timeout_s: Number of seconds to wait for an update. Returns: An updated state, or None if the timeout runs out. Raises: base_plugs.InvalidPlugError: The plug can't be waited on either because it's not in use or it's not a frontend-aware plug. """ plug_instance = self._plugs_by_name.get(plug_name) if plug_instance is None: raise base_plugs.InvalidPlugError( 'Cannot wait on unknown plug "{}".'.format(plug_name)) if not isinstance(plug_instance, base_plugs.FrontendAwareBasePlug): raise base_plugs.InvalidPlugError( 'Cannot wait on a plug {} that is not an subclass ' 'of FrontendAwareBasePlug.'.format(plug_name)) state, update_event = plug_instance.asdict_with_event() if state != remote_state: return state if update_event.wait(timeout_s): return plug_instance._asdict()
def initialize_plugs( self, plug_types: Optional[Set[Type[base_plugs.BasePlug]]] = None ) -> None: """Instantiate required plugs. Instantiates plug types and saves the instances in self._plugs_by_type for use in provide_plugs(). Args: plug_types: Plug types may be specified here rather than passed into the constructor (this is used primarily for unit testing phases). """ types = plug_types if plug_types is not None else self._plug_types for plug_type in types: # Create a logger for this plug. All plug loggers go under the 'plug' # sub-logger in the logger hierarchy. plug_logger = self.logger.getChild(plug_type.__name__) if plug_type in self._plugs_by_type: continue try: if not issubclass(plug_type, base_plugs.BasePlug): raise base_plugs.InvalidPlugError( 'Plug type "{}" is not an instance of base_plugs.BasePlug' .format(plug_type)) if plug_type.logger != _BASE_PLUGS_LOG: # They put a logger attribute on the class itself, overriding ours. raise base_plugs.InvalidPlugError( 'Do not override "logger" in your plugs.', plug_type) # Override the logger so that __init__'s logging goes into the record. plug_type.logger = plug_logger try: plug_instance = plug_type() finally: # Now set it back since we'll give the instance a logger in a moment. plug_type.logger = _BASE_PLUGS_LOG # Set the logger attribute directly (rather than in base_plugs.BasePlug) # so we don't depend on subclasses' implementation of __init__ to have # it set. if plug_instance.logger != _BASE_PLUGS_LOG: raise base_plugs.InvalidPlugError( 'Do not set "self.logger" in __init__ in your plugs', plug_type) else: # Now the instance has its own copy of the test logger. plug_instance.logger = plug_logger except Exception: # pylint: disable=broad-except plug_logger.exception('Exception instantiating plug type %s', plug_type) self.tear_down_plugs() raise self.update_plug(plug_type, plug_instance)
def __getattr__(self, attr): if attr == '_device': # _device was not found in the instance attributes. raise DeviceWrappingPlugNotFullyInitializedError( f'{type(self)} must set _device. This is genally done in __init__ by ' 'calling super().__init__(device)') if self._device is None: raise base_plugs.InvalidPlugError( 'DeviceWrappingPlug instances must set the _device attribute.') attribute = getattr(self._device, attr) if not self.verbose or not isinstance(attribute, types.MethodType): return attribute # Attribute callable; return a wrapper that logs calls with args and kwargs. functools.wraps(attribute, assigned=('__name__', '__doc__')) def logging_wrapper(*args, **kwargs): """Wraps a callable with a logging statement.""" args_strings = tuple(short_repr(arg) for arg in args) kwargs_strings = tuple(('%s=%s' % (key, short_repr(val)) for key, val in kwargs.items())) log_line = '%s calling "%s" on device.' % (type(self).__name__, attr) if args_strings or kwargs_strings: log_line += ' Args: \n %s' % (', '.join(args_strings + kwargs_strings)) self.logger.debug(log_line) return attribute(*args, **kwargs) return logging_wrapper
def with_plugs(self, **subplugs: Type[base_plugs.BasePlug]) -> 'PhaseDescriptor': """Substitute plugs for placeholders for this phase. Args: **subplugs: dict of plug name to plug class, plug classes to replace; unknown plug names are ignored. A base_plugs.InvalidPlugError is raised when a test includes a phase that still has a placeholder plug. Raises: base_plugs.InvalidPlugError: if for one of the plug names one of the following is true: - The new plug subclass is not a subclass of the original. - The original plug class is not a placeholder or automatic placeholder. Returns: PhaseDescriptor with updated plugs. """ plugs_by_name = {plug.name: plug for plug in self.plugs} new_plugs = {} for name, sub_class in six.iteritems(subplugs): original_plug = plugs_by_name.get(name) accept_substitute = True if original_plug is None: continue elif isinstance(original_plug.cls, base_plugs.PlugPlaceholder): accept_substitute = issubclass(sub_class, original_plug.cls.base_class) else: # Check __dict__ to see if the attribute is explicitly defined in the # class, rather than being defined in a parent class. accept_substitute = ( 'auto_placeholder' in original_plug.cls.__dict__ and original_plug.cls.auto_placeholder and issubclass(sub_class, original_plug.cls)) if not accept_substitute: raise base_plugs.InvalidPlugError( 'Could not find valid placeholder for substitute plug %s ' 'required for phase %s' % (name, self.name)) new_plugs[name] = data.attr_copy(original_plug, cls=sub_class) if not new_plugs: return self plugs_by_name.update(new_plugs) return data.attr_copy( self, plugs=list(plugs_by_name.values()), options=self.options.format_strings(**subplugs), measurements=[m.with_args(**subplugs) for m in self.measurements])
def __init__(self, plug_types: Set[Type[base_plugs.BasePlug]] = None, record_logger: Optional[logging.Logger] = None): self._plug_types = plug_types or set() for plug_type in self._plug_types: if isinstance(plug_type, base_plugs.PlugPlaceholder): raise base_plugs.InvalidPlugError( 'Plug {} is a placeholder, replace it using with_plugs().'. format(plug_type)) self._plugs_by_type = {} self._plugs_by_name = {} self._plug_descriptors = {} if not record_logger: record_logger = _LOG self.logger = record_logger.getChild('plug')
def plug( update_kwargs: bool = True, **plugs_map: Union[Type[base_plugs.BasePlug], base_plugs.PlugPlaceholder] ) -> Callable[['phase_descriptor.PhaseT'], 'phase_descriptor.PhaseDescriptor']: """Creates a decorator that passes in plugs when invoked. This function returns a decorator for a function that will replace positional arguments to that function with the plugs specified. See the module docstring for details and examples. Note this decorator does not work with class or bound methods, but does work with @staticmethod. Args: update_kwargs: If true, makes the decorated phase take this plug as a kwarg. **plugs_map: Dict mapping name to Plug type. Returns: A PhaseDescriptor that will pass plug instances in as kwargs when invoked. Raises: base_plugs.InvalidPlugError: If a type is provided that is not a subclass of BasePlug. """ for a_plug in plugs_map.values(): if not (isinstance(a_plug, base_plugs.PlugPlaceholder) or issubclass(a_plug, base_plugs.BasePlug)): raise base_plugs.InvalidPlugError( 'Plug %s is not a subclass of base_plugs.BasePlug nor a placeholder ' 'for one' % a_plug) def result( func: 'phase_descriptor.PhaseT' ) -> 'phase_descriptor.PhaseDescriptor': """Wrap the given function and return the wrapper. Args: func: The function to wrap. Returns: A PhaseDescriptor that, when called will invoke the wrapped function, passing plugs as keyword args. Raises: DuplicatePlugError: If a plug name is declared twice for the same function. """ phase = phase_descriptor.PhaseDescriptor.wrap_or_copy(func) duplicates = (frozenset(p.name for p in phase.plugs) & frozenset(plugs_map)) if duplicates: raise DuplicatePlugError( 'Plugs %s required multiple times on phase %s' % (duplicates, func)) phase.plugs.extend([ base_plugs.PhasePlug(name, a_plug, update_kwargs=update_kwargs) for name, a_plug in six.iteritems(plugs_map) ]) return phase return result