Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Internal change #1004

Merged
merged 1 commit into from
May 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed contrib/plugs/__init__.py
Empty file.
4 changes: 2 additions & 2 deletions examples/all_the_things.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from six.moves import zip


@htf.plug(example=example_plugs.ExamplePlug)
@htf.plug(example=example_plugs.example_plug_configured)
@htf.plug(frontend_aware=example_plugs.ExampleFrontendAwarePlug)
def example_monitor(example, frontend_aware):
time.sleep(.2)
Expand All @@ -50,7 +50,7 @@ def example_monitor(example, frontend_aware):
docstring='Helpful docstring',
units=units.HERTZ,
validators=[util.validators.matches_regex('Measurement')])
@htf.plug(example=example_plugs.ExamplePlug)
@htf.plug(example=example_plugs.example_plug_configured)
@htf.plug(prompts=user_input.UserInput)
def hello_world(test, example, prompts):
"""A hello world test phase."""
Expand Down
24 changes: 12 additions & 12 deletions examples/example_plugs.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,27 @@
"""Example plugs for OpenHTF."""

from openhtf.core import base_plugs
from openhtf.util import conf
from openhtf.util import configuration

conf.declare(
CONF = configuration.CONF

EXAMPLE_PLUG_INCREMENT_SIZE = CONF.declare(
'example_plug_increment_size',
default_value=1,
description='increment constant for example plug.')


class ExamplePlug(base_plugs.BasePlug): # pylint: disable=no-init
class ExamplePlug(base_plugs.BasePlug):
"""Example of a simple plug.

This plug simply keeps a value and increments it each time increment() is
called. You'll notice a few paradigms here:

- @conf.inject_positional_args
- configuration.bind_init_args
This is generally a good way to pass in any configuration that your
plug needs, such as an IP address or serial port to connect to. If
You want to use your plug outside of the OpenHTF framework, you can
still manually instantiate it, but you must pass the arguments by
keyword (as a side effect of the way inject_positional_args is
implemented).

For example, if you had no openhtf.conf loaded, you could do this:
my_plug = ExamplePlug(example_plug_increment_size=4)
you want to use your plug outside of the OpenHTF framework, you can
still manually instantiate it.

- tearDown()
This method will be called automatically by the OpenHTF framework at
Expand All @@ -61,7 +58,6 @@ class ExamplePlug(base_plugs.BasePlug): # pylint: disable=no-init
a with: block at the beginning of every phase where it is used.
"""

@conf.inject_positional_args
def __init__(self, example_plug_increment_size):
self.increment_size = example_plug_increment_size
self.value = 0
Expand All @@ -79,6 +75,10 @@ def increment(self):
return self.value - self.increment_size


example_plug_configured = configuration.bind_init_args(
ExamplePlug, EXAMPLE_PLUG_INCREMENT_SIZE)


class ExampleFrontendAwarePlug(base_plugs.FrontendAwareBasePlug):
"""Example of a simple frontend-aware plug.

Expand Down
6 changes: 4 additions & 2 deletions examples/frontend_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
from openhtf.output.servers import station_server
from openhtf.output.web_gui import web_launcher
from openhtf.plugs import user_input
from openhtf.util import conf
from openhtf.util import configuration

CONF = configuration.CONF


@htf.measures(htf.Measurement('hello_world_measurement'))
Expand All @@ -27,7 +29,7 @@ def hello_world(test):


def main():
conf.load(station_server_port='4444')
CONF.load(station_server_port='4444')
with station_server.StationServer() as server:
web_launcher.launch('http://localhost:4444')
for _ in range(5):
Expand Down
8 changes: 5 additions & 3 deletions examples/stop_on_first_failure.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,17 @@
test.configure(stop_on_first_failure=True)

2. Using config item. This option lets you toggle this feature dynamically.
conf.load(stop_on_first_failure=True)
CONF.load(stop_on_first_failure=True)
"""

import openhtf as htf
from openhtf.output.callbacks import console_summary
from openhtf.plugs import user_input
from openhtf.util import conf # pylint: disable=unused-import
from openhtf.util import configuration
from openhtf.util import validators

CONF = configuration.CONF


@htf.measures('number_sum', validators=[validators.in_range(0, 5)])
def add_numbers_fails(test):
Expand Down Expand Up @@ -58,7 +60,7 @@ def main():

# Option 2 : You can disable option 1 and enable below line
# to get same result
# conf.load(stop_on_first_failure=True)
# CONF.load(stop_on_first_failure=True)

test.execute(test_start=user_input.prompt_for_test_start())

Expand Down
6 changes: 3 additions & 3 deletions openhtf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from openhtf.core.phase_descriptor import diagnose
from openhtf.core.phase_descriptor import measures
from openhtf.core.phase_descriptor import PhaseDescriptor
from openhtf.core.phase_descriptor import PhaseNameCase
from openhtf.core.phase_descriptor import PhaseOptions
from openhtf.core.phase_descriptor import PhaseResult
from openhtf.core.phase_group import PhaseGroup
Expand All @@ -50,16 +51,15 @@
from openhtf.core.test_record import PhaseRecord
from openhtf.core.test_record import TestRecord
from openhtf.plugs import plug
from openhtf.util import conf
from openhtf.util import configuration
from openhtf.util import console_output
from openhtf.util import data
from openhtf.util import functions
from openhtf.util import logs
from openhtf.util import units
import pkg_resources

if typing.TYPE_CHECKING:
conf: conf.Configuration # Configuration is only available here in typing.
conf = configuration.CONF


def get_version():
Expand Down
45 changes: 31 additions & 14 deletions openhtf/core/base_plugs.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,30 +60,37 @@ def TestPhase(test, example):
Tearing down ExamplePlug!

Plugs will often need to use configuration values. The recommended way
of doing this is with the conf.inject_positional_args decorator:
of doing this is with the configuration.inject_positional_args decorator:

from openhtf import plugs
from openhtf.util import conf
from openhtf.util import configuration

conf.declare('my_config_key', default_value='my_config_value')
CONF = configuration.CONF
MY_CONFIG_KEY = CONF.declare('my_config_key', default_value='my_config_value')

CONF.declare('my_config_key', default_value='my_config_value')

class ExamplePlug(base_plugs.BasePlug):
'''A plug that requires some configuration.'''

@conf.inject_positional_args
def __init__(self, my_config_key)
self._my_config = my_config_key

Note that Plug constructors shouldn't take any other arguments; the
framework won't pass any, so you'll get a TypeError. Any values that are only
known at run time must be either passed into other methods or set via explicit
setter methods. See openhtf/conf.py for details, but with the above
example, you would also need a configuration .yaml file with something like:
example_plug_configured = configuration.bind_init_args(
ExamplePlug, MY_CONFIG_KEY)

Here, example_plug_configured is a subclass of ExamplePlug with bound args for
the initializer, and it can be passed to phases like any other plug. See
openhtf/conf.py for details, but with the above example, you would also need a
configuration .yaml file with something like:

my_config_key: my_config_value

This will result in the ExamplePlug being constructed with
This will result in the example_plug_configured being constructed with
self._my_config having a value of 'my_config_value'.

Note that Plug constructors shouldn't take any other arguments; the
framework won't pass any, so you'll get a TypeError.
"""

import logging
Expand All @@ -103,6 +110,18 @@ class InvalidPlugError(Exception):
class BasePlug(object):
"""All plug types must subclass this type.

Okay to use with multiple inheritance when subclassing an existing
implementation that you want to convert into a plug. Place BasePlug last in
the parent list. For example:

class MyExistingDriver:
def do_something(self):
pass

class MyExistingDriverPlug(MyExistingDriver, BasePlug):
def tearDown(self):
... # Implement the BasePlug interface as desired.

Attributes:
logger: This attribute will be set by the PlugManager (and as such it
doesn't appear here), and is the same logger as passed into test phases
Expand Down Expand Up @@ -147,14 +166,12 @@ def _asdict(self) -> Dict[Text, Any]:

def tearDown(self) -> None:
"""This method is called automatically at the end of each Test execution."""
pass

@classmethod
def uses_base_tear_down(cls) -> bool:
"""Checks whether the tearDown method is the BasePlug implementation."""
this_tear_down = getattr(cls, 'tearDown')
base_tear_down = getattr(BasePlug, 'tearDown')
return this_tear_down.__code__ is base_tear_down.__code__
this_tear_down = getattr(cls, BasePlug.tearDown.__name__)
return this_tear_down.__code__ is BasePlug.tearDown.__code__


class FrontendAwareBasePlug(BasePlug, util.SubscribableStateMixin):
Expand Down
1 change: 0 additions & 1 deletion openhtf/core/diagnoses_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# Lint as: python3
"""Diagnoses: Measurement and meta interpreters.

Diagnoses are higher level signals that result from processing multiple
Expand Down
2 changes: 1 addition & 1 deletion openhtf/core/measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ def validate(self) -> 'Measurement':
raise
finally:
if self._cached:
self._cached['outcome'] = self.outcome.name
self._cached['outcome'] = self.outcome.name # pytype: disable=bad-return-type

def as_base_types(self) -> Dict[Text, Any]:
"""Convert this measurement to a dict of basic types."""
Expand Down
1 change: 0 additions & 1 deletion openhtf/core/phase_branches.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# Lint as: python3
"""Implements phase node branches.

A BranchSequence is a phase node sequence that runs conditiionally based on the
Expand Down
1 change: 0 additions & 1 deletion openhtf/core/phase_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# Lint as: python3
"""Implements the basic PhaseNode collections.

Phase Sequence are basic collections where each node is sequentially run. These
Expand Down
45 changes: 39 additions & 6 deletions openhtf/core/phase_descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Text, TYPE_CHECKING, Type, Union

import attr
import inflection

import openhtf
from openhtf import util
Expand Down Expand Up @@ -73,6 +74,15 @@ class PhaseResult(enum.Enum):
FAIL_SUBTEST = 'FAIL_SUBTEST'


@enum.unique
class PhaseNameCase(enum.Enum):
"""Options for formatting casing for phase names."""
# Does not modify case for phase name.
KEEP = 'KEEP'
# Changes phase name case to CamelCase.
CAMEL = 'CAMEL'


PhaseReturnT = Optional[PhaseResult]
PhaseCallableT = Callable[..., PhaseReturnT]
PhaseCallableOrNodeT = Union[PhaseCallableT, phase_nodes.PhaseNode]
Expand Down Expand Up @@ -101,6 +111,8 @@ class PhaseOptions(object):
run_under_pdb: If True, run the phase under the Python Debugger (pdb). When
setting this option, increase the phase timeout as well because the
timeout will still apply when under the debugger.
phase_name_case: Case formatting options for phase name.
stop_on_measurement_fail: Whether to stop the test if any measurements fail.
Example Usages: @PhaseOptions(timeout_s=1)
def PhaseFunc(test): pass @PhaseOptions(name='Phase({port})')
def PhaseFunc(test, port, other_info): pass
Expand All @@ -114,6 +126,8 @@ def PhaseFunc(test, port, other_info): pass
repeat_on_timeout = attr.ib(type=bool, default=False)
repeat_limit = attr.ib(type=Optional[int], default=None)
run_under_pdb = attr.ib(type=bool, default=False)
phase_name_case = attr.ib(type=PhaseNameCase, default=PhaseNameCase.KEEP)
stop_on_measurement_fail = attr.ib(type=bool, default=False)

def format_strings(self, **kwargs: Any) -> 'PhaseOptions':
"""String substitution of name."""
Expand All @@ -139,6 +153,11 @@ def __call__(self, phase_func: PhaseT) -> 'PhaseDescriptor':
phase.options.repeat_limit = self.repeat_limit
if self.run_under_pdb:
phase.options.run_under_pdb = self.run_under_pdb
if self.phase_name_case == PhaseNameCase.CAMEL:
name = phase.name if phase.options.name is None else phase.options.name
phase.options.name = inflection.camelize(name)
if self.stop_on_measurement_fail:
phase.options.stop_on_measurement_fail = self.stop_on_measurement_fail
return phase


Expand Down Expand Up @@ -202,7 +221,7 @@ def wrap_or_copy(cls, func: PhaseT, **options: Any) -> 'PhaseDescriptor':
return retval

def _asdict(self) -> Dict[Text, Any]:
ret = attr.asdict(self, filter=attr.filters.exclude('func'))
ret = attr.asdict(self, filter=attr.filters.exclude('func')) # pytype: disable=wrong-arg-types # attr-stubs
ret.update(name=self.name, doc=self.doc)
return ret

Expand Down Expand Up @@ -336,6 +355,7 @@ def __call__(self,
kwargs.update(
running_test_state.plug_manager.provide_plugs(
(plug.name, plug.cls) for plug in self.plugs if plug.update_kwargs))

# Pass in test_api if the phase takes *args, or **kwargs with at least 1
# positional, or more positional args than we have keyword args.
if arg_info.varargs or (keywords and len(arg_info.args) >= 1) or (len(
Expand All @@ -347,13 +367,26 @@ def __call__(self,
args.append(running_test_state.test_api)

if self.options.run_under_pdb:
return pdb.runcall(self.func, *args, **kwargs)
phase_result = pdb.runcall(self.func, *args, **kwargs)
else:
return self.func(*args, **kwargs)
if self.options.run_under_pdb:
return pdb.runcall(self.func, **kwargs)
phase_result = self.func(*args, **kwargs)

elif self.options.run_under_pdb:
phase_result = pdb.runcall(self.func, **kwargs)
else:
return self.func(**kwargs)
phase_result = self.func(**kwargs)

# Override the phase result if the user wants to treat ANY failed
# measurement of this phase as a test-stopping failure.
if self.options.stop_on_measurement_fail:
# Note: The measurement definitions do NOT have the outcome populated.
for measurement in self.measurements:
if (running_test_state.test_api.get_measurement(
measurement.name).outcome != core_measurements.Outcome.PASS):
phase_result = PhaseResult.STOP
break

return phase_result


def measures(*measurements: Union[Text, core_measurements.Measurement],
Expand Down
1 change: 0 additions & 1 deletion openhtf/core/phase_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# Lint as: python3
"""Contains the abstract interfaces for phase nodes."""

import abc
Expand Down
Loading