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

Begin adding support for shared resources in multi-slot test stations #8

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
98 changes: 52 additions & 46 deletions openhtf/core/test_descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
from openhtf.core import test_record as htf_test_record
from openhtf.core import test_state
from openhtf.core.dut_id import DutIdentifier
from openhtf.plugs import PlugManager

from openhtf.util import configuration
from openhtf.util import console_output
Expand All @@ -56,14 +57,14 @@
_LOG = logging.getLogger(__name__)

CONF.declare(
'capture_source',
description=textwrap.dedent(
"""Whether to capture the source of phases and the test module. This
defaults to False since this potentially reads many files and makes large
string copies.
'capture_source',
description=textwrap.dedent(
"""Whether to capture the source of phases and the test module. This
defaults to False since this potentially reads many files and makes large
string copies.

Set to 'true' if you want to capture your test's source."""),
default_value=False)
Set to 'true' if you want to capture your test's source."""),
default_value=False)


class MeasurementNotFoundError(Exception):
Expand Down Expand Up @@ -103,19 +104,19 @@ def create_arg_parser(add_help: bool = False) -> argparse.ArgumentParser:

"""
parser = argparse.ArgumentParser(
'OpenHTF-based testing',
parents=[
CONF.ARG_PARSER,
console_output.ARG_PARSER,
logs.ARG_PARSER,
phase_executor.ARG_PARSER,
],
add_help=add_help)
'OpenHTF-based testing',
parents=[
CONF.ARG_PARSER,
console_output.ARG_PARSER,
logs.ARG_PARSER,
phase_executor.ARG_PARSER,
],
add_help=add_help)
parser.add_argument(
'--config-help',
action='store_true',
help='Instead of executing the test, simply print all available config '
'keys and their description strings.')
'--config-help',
action='store_true',
help='Instead of executing the test, simply print all available config '
'keys and their description strings.')
return parser


Expand All @@ -141,17 +142,19 @@ def PhaseTwo(test):
DEFAULT_SIGINT_HANDLER = None

def __init__(self, *nodes: phase_descriptor.PhaseCallableOrNodeT,
plug_manager: Optional[PlugManager] = None,
**metadata: Any):
# 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.')
'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._plug_manager = plug_manager
# TODO(arsharma): Drop _flatten at some point.
sequence = phase_collections.PhaseSequence(nodes)
self._test_desc = TestDescriptor(sequence,
Expand All @@ -161,9 +164,9 @@ def __init__(self, *nodes: phase_descriptor.PhaseCallableOrNodeT,
if CONF.capture_source:
# Copy the phases with the real CodeInfo for them.
self._test_desc.phase_sequence = (
self._test_desc.phase_sequence.load_code_info())
self._test_desc.phase_sequence.load_code_info())
self._test_desc.code_info = (
htf_test_record.CodeInfo.for_module_from_stack(levels_up=2))
htf_test_record.CodeInfo.for_module_from_stack(levels_up=2))

# Make sure configure() gets called at least once before Execute(). The
# user might call configure() again to override options, but we don't want
Expand Down Expand Up @@ -253,7 +256,8 @@ def configure(self, **kwargs: Any) -> None:
def handle_sig_int(cls, signalnum: Optional[int], handler: Any) -> None:
"""Handle the SIGINT callback."""
if not cls.TEST_INSTANCES:
cls.DEFAULT_SIGINT_HANDLER(signalnum, handler) # pylint: disable=not-callable # pytype: disable=not-callable
cls.DEFAULT_SIGINT_HANDLER(signalnum,
handler) # pylint: disable=not-callable # pytype: disable=not-callable
return

_LOG.error('Received SIGINT, stopping all tests.')
Expand Down Expand Up @@ -297,10 +301,10 @@ def execute(self,
InvalidTestStateError: if this test is already being executed.
"""
phase_descriptor.check_for_duplicate_results(
self._test_desc.phase_sequence.all_phases(),
self._test_options.diagnosers)
self._test_desc.phase_sequence.all_phases(),
self._test_options.diagnosers)
phase_collections.check_for_duplicate_subtest_names(
self._test_desc.phase_sequence)
self._test_desc.phase_sequence)
# Lock this section so we don't .stop() the executor between instantiating
# it and .Start()'ing it, doing so does weird things to the executor state.
with (self._lock):
Expand Down Expand Up @@ -336,11 +340,13 @@ def trigger_phase(test):
trigger.code_info = htf_test_record.CodeInfo.for_function(trigger.func)

self._executor = test_executor.TestExecutor(
self._test_desc,
self.make_uid(),
trigger,
self._test_options,
run_with_profiling=profile_filename is not None)
self._test_desc,
self.make_uid(),
trigger,
self._test_options,
run_with_profiling=profile_filename is not None,
plug_manager=self._plug_manager,
)

_LOG.info('Executing test: %s', self.descriptor.code_info.name)
self.TEST_INSTANCES[self.uid] = self
Expand Down Expand Up @@ -377,21 +383,21 @@ def trigger_phase(test):
else:
colors = collections.defaultdict(lambda: colorama.Style.BRIGHT)
colors[htf_test_record.Outcome.PASS] = ''.join(
(colorama.Style.BRIGHT, colorama.Fore.GREEN)) # pytype: disable=wrong-arg-types
(colorama.Style.BRIGHT, colorama.Fore.GREEN)) # pytype: disable=wrong-arg-types
colors[htf_test_record.Outcome.FAIL] = ''.join(
(colorama.Style.BRIGHT, colorama.Fore.RED)) # pytype: disable=wrong-arg-types
(colorama.Style.BRIGHT, colorama.Fore.RED)) # pytype: disable=wrong-arg-types
msg_template = (
'test: {name} outcome: {color}{outcome}{marginal}{rst}')
'test: {name} outcome: {color}{outcome}{marginal}{rst}')
console_output.banner_print(
msg_template.format(
name=final_state.test_record.metadata['test_name'],
color=(colorama.Fore.YELLOW
if final_state.test_record.marginal else
colors[final_state.test_record.outcome]),
outcome=final_state.test_record.outcome.name,
marginal=(' (MARGINAL)'
if final_state.test_record.marginal else ''),
rst=colorama.Style.RESET_ALL))
msg_template.format(
name=final_state.test_record.metadata['test_name'],
color=(colorama.Fore.YELLOW
if final_state.test_record.marginal else
colors[final_state.test_record.outcome]),
outcome=final_state.test_record.outcome.name,
marginal=(' (MARGINAL)'
if final_state.test_record.marginal else ''),
rst=colorama.Style.RESET_ALL))
finally:
del self.TEST_INSTANCES[self.uid]
self._executor.close()
Expand Down Expand Up @@ -420,7 +426,7 @@ class TestOptions(object):

name = attr.ib(type=Text, default='openhtf_test')
output_callbacks = attr.ib(
type=List[Callable[[htf_test_record.TestRecord], None]], factory=list)
type=List[Callable[[htf_test_record.TestRecord], None]], factory=list)
failure_exceptions = attr.ib(type=List[Type[Exception]], factory=list)
default_dut_id = attr.ib(type=Text, default='UNKNOWN_DUT')
stop_on_first_failure = attr.ib(type=bool, default=False)
Expand Down Expand Up @@ -574,7 +580,7 @@ def attach_from_file(
IOError: Raised if the given filename couldn't be opened.
"""
self._running_phase_state.attach_from_file(
filename, name=name, mimetype=mimetype)
filename, name=name, mimetype=mimetype)

def get_measurement(
self,
Expand Down Expand Up @@ -611,7 +617,7 @@ def get_measurement_strict(
measurement = self._running_test_state.get_measurement(measurement_name)
if measurement is None:
raise MeasurementNotFoundError(
f'Failed to find test measurement {measurement_name}')
f'Failed to find test measurement {measurement_name}')
return measurement

def get_attachment(
Expand Down
8 changes: 6 additions & 2 deletions openhtf/core/test_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from openhtf.core import phase_nodes
from openhtf.core import test_record
from openhtf.core import test_state
from openhtf.plugs import PlugManager
from openhtf.util import configuration
from openhtf.util import threads

Expand Down Expand Up @@ -96,10 +97,11 @@ def __init__(self, test_descriptor: 'test_descriptor.TestDescriptor',
execution_uid: Text,
test_start: Optional[phase_descriptor.PhaseDescriptor],
test_options: 'test_descriptor.TestOptions',
run_with_profiling: bool):
run_with_profiling: bool, plug_manager: Optional[PlugManager] = None):
super(TestExecutor, self).__init__(
name='TestExecutorThread', run_with_profiling=run_with_profiling)
self.test_state = None # type: Optional[test_state.TestState]
self._plug_manager = plug_manager

self._test_descriptor = test_descriptor
self._test_start = test_start
Expand Down Expand Up @@ -201,7 +203,9 @@ def _thread_proc(self) -> None:
try:
# Top level steps required to run a single iteration of the Test.
self.test_state = test_state.TestState(self._test_descriptor, self.uid,
self._test_options)
self._test_options,
plug_manager=self._plug_manager
)
phase_exec = phase_executor.PhaseExecutor(self.test_state)

# Any access to self._exit_stacks must be done while holding this lock.
Expand Down
9 changes: 7 additions & 2 deletions openhtf/core/test_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ class Status(enum.Enum):

def __init__(self, test_desc: 'test_descriptor.TestDescriptor',
execution_uid: Text,
test_options: 'test_descriptor.TestOptions'):
test_options: 'test_descriptor.TestOptions',
plug_manager: Optional[plugs.PlugManager] = None):
"""Initializer.

Args:
Expand All @@ -184,7 +185,11 @@ def __init__(self, test_desc: 'test_descriptor.TestDescriptor',
logs.initialize_record_handler(execution_uid, self.test_record,
self.notify_update)
self.state_logger = logs.get_record_logger_for(execution_uid)
self.plug_manager = plugs.PlugManager(test_desc.plug_types,
if plug_manager is not None:
self.plug_manager = plug_manager
self.plug_manager.initialize_plugs(test_desc.plug_types)
else:
self.plug_manager = plugs.PlugManager(test_desc.plug_types,
self.state_logger)
self.diagnoses_manager = diagnoses_lib.DiagnosesManager(
self.state_logger.getChild('diagnoses'))
Expand Down
Loading