def with_plugs(self, **subplugs): """Substitute plugs for placeholders for this phase.""" plugs_by_name = {plug.name: plug for plug in self.plugs} new_plugs = dict(plugs_by_name) for name, sub_class in subplugs.items(): original_plug = plugs_by_name.get(name) accept_substitute = True if original_plug is None: accept_substitute = False elif isinstance(original_plug.cls, 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 plugs.InvalidPlugError( 'Could not find valid placeholder for substitute plug %s ' 'required for phase %s' % (name, self.name)) new_plugs[name] = mutablerecords.CopyRecord(original_plug, cls=sub_class) return mutablerecords.CopyRecord( self, plugs=list(new_plugs.values()), options=self.options.format_strings(**subplugs), measurements=[m.with_args(**subplugs) for m in self.measurements])
def testCopyRecord(self): obj = Name('name') new_obj = mutablerecords.CopyRecord(obj) self.assertIsNot(obj, new_obj) self.assertIsInstance(Recursive().subrec, Simple) self.assertEqual(Recursive().lst, []) rec_obj = Recursive() new_rec_obj = mutablerecords.CopyRecord(rec_obj) self.assertIsNot(rec_obj, new_rec_obj) self.assertIsNot(rec_obj.subrec, new_rec_obj.subrec) self.assertIsNot(rec_obj.lst, new_rec_obj.lst) self.assertEqual(rec_obj.lst, [])
def _apply_with_plugs(self, subplugs, error_on_unknown): """Substitute plugs for placeholders for this phase. Args: subplugs: dict of plug name to plug class, plug classes to replace. error_on_unknown: bool, if True, then error when an unknown plug name is provided. Raises: openhtf.plugs.InvalidPlugError if for one of the plug names one of the following is true: - error_on_unknown is True and the plug name is not registered. - 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 = dict(plugs_by_name) for name, sub_class in six.iteritems(subplugs): original_plug = plugs_by_name.get(name) accept_substitute = True if original_plug is None: if not error_on_unknown: continue accept_substitute = False elif isinstance(original_plug.cls, openhtf.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 openhtf.plugs.InvalidPlugError( 'Could not find valid placeholder for substitute plug %s ' 'required for phase %s' % (name, self.name)) new_plugs[name] = mutablerecords.CopyRecord(original_plug, cls=sub_class) return mutablerecords.CopyRecord( self, plugs=list(new_plugs.values()), options=self.options.format_strings(**subplugs), measurements=[m.with_args(**subplugs) for m in self.measurements])
def convert_if_not(cls, phases_or_groups): """Convert list of phases or groups into a new PhaseGroup if not already.""" if isinstance(phases_or_groups, PhaseGroup): return mutablerecords.CopyRecord(phases_or_groups) flattened = flatten_phases_and_groups(phases_or_groups) return cls(main=flattened)
def wrap_or_copy(cls, func, **options): """Return a new PhaseDescriptor from the given function or instance. We want to return a new copy so that you can reuse a phase with different options, plugs, measurements, etc. Args: func: A phase function or PhaseDescriptor instance. **options: Options to update on the result. Raises: PhaseWrapError: if func is a openhtf.PhaseGroup. Returns: A new PhaseDescriptor object. """ if isinstance(func, openhtf.PhaseGroup): raise PhaseWrapError('Cannot wrap PhaseGroup <%s> as a phase.' % ( func.name or 'Unnamed')) if isinstance(func, cls): # We want to copy so that a phase can be reused with different options # or kwargs. See with_args() below for more details. retval = mutablerecords.CopyRecord(func) else: retval = cls(func) retval.options.update(**options) return retval
def with_args(self, **kwargs): """Send these keyword-arguments to the phase when called.""" # Make a copy so we can have multiple of the same phase with different args # in the same test. new_info = mutablerecords.CopyRecord(self) new_info.options = new_info.options.format_strings(**kwargs) new_info.extra_kwargs.update(kwargs) new_info.measurements = [m.with_args(**kwargs) for m in self.measurements] return new_info
def FromMeasurement(cls, measurement): measured_value = measurement.measured_value if isinstance(measured_value, measurements.DimensionedMeasuredValue): value = mutablerecords.CopyRecord(measured_value, value_dict=copy.deepcopy( measured_value.value_dict)) else: value = (copy.deepcopy(measured_value.value) if measured_value.is_value_set else None) return cls(measurement.name, value, measurement.units, measurement.dimensions, measurement.outcome)
def with_args(self, **kwargs): """String substitution for names and docstrings.""" validators = [ validator.with_args(**kwargs) if hasattr(validator, 'with_args') else validator for validator in self.validators ] return mutablerecords.CopyRecord( self, name=util.format_string(self.name, kwargs), docstring=util.format_string(self.docstring, kwargs), validators=validators, )
def WithArgs(self, **kwargs): """Creates a new Measurement, see openhtf.PhaseInfo.WithArgs.""" new_meas = mutablerecords.CopyRecord(self) if '{' in new_meas.name: formatter = lambda x: x.format(**kwargs) if x else x else: # str % {'a': 1} is harmless if str doesn't use any interpolation. # .format is as well, but % is more likely to be used in other contexts. formatter = lambda x: x % kwargs if x else x new_meas.name = formatter(self.name) new_meas.docstring = formatter(self.docstring) return new_meas
def with_plugs(self, **subplugs): """Substitute plugs for placeholders for this phase.""" plugs_by_name = {plug.name: plug for plug in self.plugs} new_plugs = dict(plugs_by_name) for name, sub_class in subplugs.iteritems(): original_plug = plugs_by_name.get(name) if (original_plug is None or not isinstance(original_plug.cls, plugs.PlugPlaceholder) or not issubclass(sub_class, original_plug.cls.base_class)): raise plugs.InvalidPlugError( 'Could not find valid placeholder for substitute plug %s ' 'required for phase %s' % (name, self.name)) new_plugs[name] = mutablerecords.CopyRecord(original_plug, cls=sub_class) return mutablerecords.CopyRecord( self, plugs=new_plugs.values(), options=self.options.format_strings(**subplugs), measurements=[m.with_args(**subplugs) for m in self.measurements])
def load_code_info(phases_or_groups): """Recursively load code info for a PhaseGroup or list of phases or groups.""" if isinstance(phases_or_groups, PhaseGroup): return phases_or_groups.load_code_info() ret = [] for phase in phases_or_groups: if isinstance(phase, PhaseGroup): ret.append(phase.load_code_info()) else: ret.append( mutablerecords.CopyRecord( phase, code_info=test_record.CodeInfo.for_function(phase.func))) return ret
def WrapOrCopy(cls, func): """Return a new PhaseInfo from the given function or instance. We want to return a new copy so that you can reuse a phase with different options, plugs, measurements, etc. Args: func: A phase function or PhaseInfo instance. Returns: A new PhaseInfo object. """ if not isinstance(func, cls): func = cls(func, test_record.CodeInfo.ForFunction(func)) # We want to copy so that a phase can be reused with different options, etc. return mutablerecords.CopyRecord(func)
def with_args(self, **kwargs): """String substitution for names and docstrings.""" new_validators = [ v.with_args(**kwargs) if hasattr(v, 'with_args') else v for v in self.validators ] new_conditional_validators = [ cv.with_args(**kwargs) for cv in self.conditional_validators ] return mutablerecords.CopyRecord( self, name=util.format_string(self.name, kwargs), docstring=util.format_string(self.docstring, kwargs), validators=new_validators, conditional_validators=new_conditional_validators, _cached=None, )
def _serialize_state_dict(state_dict, remote_record=None): if (remote_record and remote_record['start_time_millis'] == state_dict['test_record'].start_time_millis): # Make a copy and delete phases/logs that are already known remotely. state_dict['test_record'] = mutablerecords.CopyRecord( state_dict['test_record']) del state_dict['test_record'].phases[:remote_record['phases']] del state_dict['test_record'].log_records[:remote_record['log_records']] return { 'status': state_dict['status'].name, 'test_record': data.convert_to_base_types(state_dict['test_record']), 'plugs': state_dict['plugs'], 'running_phase_state': data.convert_to_base_types(state_dict['running_phase_state']) }
def __init__(self, *phases, **metadata): # Some sanity checks on special metadata keys we automatically fill in. if 'config' in metadata: raise KeyError( 'Invalid metadata key "config", it will be automatically populated.' ) self.created_time_millis = util.time_millis() self.last_run_time_millis = None self._test_options = TestOptions() self._lock = threading.Lock() self._executor = None self._test_desc = TestDescriptor(phases, test_record.CodeInfo.uncaptured(), metadata) if conf.capture_source: # First, we copy the phases with the real CodeInfo for them. phases = [ mutablerecords.CopyRecord( phase, code_info=test_record.CodeInfo.for_function(phase.func)) for phase in self._test_desc.phases ] # Then we replace the TestDescriptor with one that stores the test # module's CodeInfo as well as our newly copied phases. code_info = test_record.CodeInfo.for_module_from_stack(levels_up=2) self._test_desc = self._test_desc._replace(code_info=code_info, phases=phases) # Make sure configure() gets called at least once before Execute(). The # user might call configure() again to override options, but we don't want # to force them to if they want to use defaults. For default values, see # the class definition of TestOptions. if 'test_name' in metadata: # Allow legacy metadata key for specifying test name. self.configure(name=metadata['test_name']) else: self.configure() # This is a noop if the server is already running, otherwise start it now # that we have at least one Test instance. station_api.start_server()
def wrap_or_copy(cls, func, **options): """Return a new PhaseDescriptor from the given function or instance. We want to return a new copy so that you can reuse a phase with different options, plugs, measurements, etc. Args: func: A phase function or PhaseDescriptor instance. **options: Options to update on the result. Returns: A new PhaseDescriptor object. """ if isinstance(func, cls): # We want to copy so that a phase can be reused with different options # or kwargs. See with_args() below for more details. retval = mutablerecords.CopyRecord(func) else: retval = cls(func) retval.options.update(**options) return retval
def format_strings(self, **kwargs): """String substitution of name.""" return mutablerecords.CopyRecord( self, name=util.format_string(self.name, kwargs))
def format_strings(self, **kwargs): """String substitution for names and docstrings.""" return mutablerecords.CopyRecord( self, name=util.format_string(self.name, kwargs), docstring=util.format_string(self.docstring, kwargs))