Skip to content

Commit 6f6729c

Browse files
committed
Move signal.signal() registration from package __init__ to Test.execute
1 parent 05b2a06 commit 6f6729c

File tree

2 files changed

+86
-76
lines changed

2 files changed

+86
-76
lines changed

openhtf/__init__.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
"""The main OpenHTF entry point."""
1717

1818
import pkg_resources
19-
import signal
2019

2120
from openhtf import plugs
2221
from openhtf.core import phase_executor
@@ -55,6 +54,3 @@ def get_version():
5554
return 'Unknown - Perhaps openhtf was not installed via setup.py or pip.'
5655

5756
__version__ = get_version()
58-
59-
# Register signal handler to stop all tests on SIGINT.
60-
signal.signal(signal.SIGINT, Test.handle_sig_int)

openhtf/core/test_descriptor.py

Lines changed: 86 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
"""
2222
import argparse
2323
import collections
24+
import contextlib
2425
import logging
2526
import os
27+
import signal
2628
import sys
2729
import textwrap
2830
import threading
@@ -107,6 +109,17 @@ def create_arg_parser(add_help=False):
107109
return parser
108110

109111

112+
@contextlib.contextmanager
113+
def sigint_context():
114+
"""Context within which handle_sig_int is the signal handler for SIGINT."""
115+
# Register signal handler to stop all tests on SIGINT.
116+
replaced_sigint_handler = signal.signal(signal.SIGINT, Test.handle_sig_int)
117+
try:
118+
yield
119+
finally:
120+
signal.signal(signal.SIGINT, replaced_sigint_handler)
121+
122+
110123
class Test(object):
111124
"""An object that represents an OpenHTF test.
112125
@@ -277,80 +290,81 @@ def execute(self, test_start=None):
277290
Raises:
278291
InvalidTestStateError: if this test is already being executed.
279292
"""
280-
# Lock this section so we don't .stop() the executor between instantiating
281-
# it and .Start()'ing it, doing so does weird things to the executor state.
282-
with self._lock:
283-
# Sanity check to make sure someone isn't doing something weird like
284-
# trying to Execute() the same test twice in two separate threads. We
285-
# hold the lock between here and Start()'ing the executor to guarantee
286-
# that only one thread is successfully executing the test.
287-
if self._executor:
288-
raise InvalidTestStateError('Test already running', self._executor)
289-
290-
# Snapshot some things we care about and store them.
291-
self._test_desc.metadata['test_name'] = self._test_options.name
292-
self._test_desc.metadata['config'] = conf._asdict()
293-
self.last_run_time_millis = util.time_millis()
294-
295-
if isinstance(test_start, LambdaType):
296-
@phase_descriptor.PhaseOptions()
297-
def trigger_phase(test):
298-
test.test_record.dut_id = test_start()
299-
trigger = trigger_phase
300-
else:
301-
trigger = test_start
302-
303-
if conf.capture_source:
304-
trigger.code_info = test_record.CodeInfo.for_function(trigger.func)
305-
306-
test_desc = self._get_running_test_descriptor()
307-
self._executor = test_executor.TestExecutor(
308-
test_desc, self.make_uid(), trigger, self._test_options)
309-
310-
_LOG.info('Executing test: %s', self.descriptor.code_info.name)
311-
self.TEST_INSTANCES[self.uid] = self
312-
self._executor.start()
313-
314-
try:
315-
self._executor.wait()
316-
except KeyboardInterrupt:
317-
# The SIGINT handler only raises the KeyboardInterrupt once, so only retry
318-
# that once.
319-
self._executor.wait()
320-
raise
321-
finally:
322-
try:
323-
final_state = self._executor.finalize()
324-
325-
_LOG.debug('Test completed for %s, outputting now.',
326-
final_state.test_record.metadata['test_name'])
327-
for output_cb in self._test_options.output_callbacks:
328-
try:
329-
output_cb(final_state.test_record)
330-
except Exception: # pylint: disable=broad-except
331-
_LOG.exception(
332-
'Output callback %s raised; continuing anyway', output_cb)
333-
# Make sure the final outcome of the test is printed last and in a
334-
# noticeable color so it doesn't get scrolled off the screen or missed.
335-
if final_state.test_record.outcome == test_record.Outcome.ERROR:
336-
for detail in final_state.test_record.outcome_details:
337-
console_output.error_print(detail.description)
293+
with sigint_context():
294+
# Lock this section so we don't .stop() the executor between instantiating
295+
# it and .Start()'ing it, doing so does weird things to the executor state.
296+
with self._lock:
297+
# Sanity check to make sure someone isn't doing something weird like
298+
# trying to Execute() the same test twice in two separate threads. We
299+
# hold the lock between here and Start()'ing the executor to guarantee
300+
# that only one thread is successfully executing the test.
301+
if self._executor:
302+
raise InvalidTestStateError('Test already running', self._executor)
303+
304+
# Snapshot some things we care about and store them.
305+
self._test_desc.metadata['test_name'] = self._test_options.name
306+
self._test_desc.metadata['config'] = conf._asdict()
307+
self.last_run_time_millis = util.time_millis()
308+
309+
if isinstance(test_start, LambdaType):
310+
@phase_descriptor.PhaseOptions()
311+
def trigger_phase(test):
312+
test.test_record.dut_id = test_start()
313+
trigger = trigger_phase
338314
else:
339-
colors = collections.defaultdict(lambda: colorama.Style.BRIGHT)
340-
colors[test_record.Outcome.PASS] = ''.join((colorama.Style.BRIGHT,
341-
colorama.Fore.GREEN))
342-
colors[test_record.Outcome.FAIL] = ''.join((colorama.Style.BRIGHT,
343-
colorama.Fore.RED))
344-
msg_template = 'test: {name} outcome: {color}{outcome}{rst}'
345-
console_output.banner_print(msg_template.format(
346-
name=final_state.test_record.metadata['test_name'],
347-
color=colors[final_state.test_record.outcome],
348-
outcome=final_state.test_record.outcome.name,
349-
rst=colorama.Style.RESET_ALL))
315+
trigger = test_start
316+
317+
if conf.capture_source:
318+
trigger.code_info = test_record.CodeInfo.for_function(trigger.func)
319+
320+
test_desc = self._get_running_test_descriptor()
321+
self._executor = test_executor.TestExecutor(
322+
test_desc, self.make_uid(), trigger, self._test_options)
323+
324+
_LOG.info('Executing test: %s', self.descriptor.code_info.name)
325+
self.TEST_INSTANCES[self.uid] = self
326+
self._executor.start()
327+
328+
try:
329+
self._executor.wait()
330+
except KeyboardInterrupt:
331+
# The SIGINT handler only raises the KeyboardInterrupt once, so only retry
332+
# that once.
333+
self._executor.wait()
334+
raise
350335
finally:
351-
del self.TEST_INSTANCES[self.uid]
352-
self._executor.close()
353-
self._executor = None
336+
try:
337+
final_state = self._executor.finalize()
338+
339+
_LOG.debug('Test completed for %s, outputting now.',
340+
final_state.test_record.metadata['test_name'])
341+
for output_cb in self._test_options.output_callbacks:
342+
try:
343+
output_cb(final_state.test_record)
344+
except Exception: # pylint: disable=broad-except
345+
_LOG.exception(
346+
'Output callback %s raised; continuing anyway', output_cb)
347+
# Make sure the final outcome of the test is printed last and in a
348+
# noticeable color so it doesn't get scrolled off the screen or missed.
349+
if final_state.test_record.outcome == test_record.Outcome.ERROR:
350+
for detail in final_state.test_record.outcome_details:
351+
console_output.error_print(detail.description)
352+
else:
353+
colors = collections.defaultdict(lambda: colorama.Style.BRIGHT)
354+
colors[test_record.Outcome.PASS] = ''.join((colorama.Style.BRIGHT,
355+
colorama.Fore.GREEN))
356+
colors[test_record.Outcome.FAIL] = ''.join((colorama.Style.BRIGHT,
357+
colorama.Fore.RED))
358+
msg_template = 'test: {name} outcome: {color}{outcome}{rst}'
359+
console_output.banner_print(msg_template.format(
360+
name=final_state.test_record.metadata['test_name'],
361+
color=colors[final_state.test_record.outcome],
362+
outcome=final_state.test_record.outcome.name,
363+
rst=colorama.Style.RESET_ALL))
364+
finally:
365+
del self.TEST_INSTANCES[self.uid]
366+
self._executor.close()
367+
self._executor = None
354368

355369
return final_state.test_record.outcome == test_record.Outcome.PASS
356370

0 commit comments

Comments
 (0)