Beispiel #1
0
    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()
Beispiel #2
0
    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)
Beispiel #3
0
  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
Beispiel #4
0
    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])
Beispiel #5
0
 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')
Beispiel #6
0
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