Skip to content

Commit a60148d

Browse files
authored
Internal change (#1037)
PiperOrigin-RevId: 474850134
1 parent 78113cf commit a60148d

File tree

8 files changed

+101
-57
lines changed

8 files changed

+101
-57
lines changed

openhtf/core/phase_executor.py

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
framework.
3131
"""
3232

33-
import logging
3433
import pstats
3534
import sys
3635
import threading
@@ -63,9 +62,6 @@
6362
target='%s.DEFAULT_PHASE_TIMEOUT_S' % __name__,
6463
help='Test phase timeout in seconds')
6564

66-
# TODO(arsharma): Use the test state logger.
67-
_LOG = logging.getLogger(__name__)
68-
6965

7066
@attr.s(slots=True, frozen=True)
7167
class ExceptionInfo(object):
@@ -170,7 +166,8 @@ def __init__(self, phase_desc: phase_descriptor.PhaseDescriptor,
170166
subtest_rec: Optional[test_record.SubtestRecord]):
171167
super(PhaseExecutorThread, self).__init__(
172168
name='<PhaseExecutorThread: (phase_desc.name)>',
173-
run_with_profiling=run_with_profiling)
169+
run_with_profiling=run_with_profiling,
170+
logger=test_state.state_logger.getChild('phase_executor_thread'))
174171
self._phase_desc = phase_desc
175172
self._test_state = test_state
176173
self._subtest_rec = subtest_rec
@@ -238,6 +235,7 @@ class PhaseExecutor(object):
238235

239236
def __init__(self, test_state: 'htf_test_state.TestState'):
240237
self.test_state = test_state
238+
self.logger = test_state.state_logger.getChild('phase_executor')
241239
# This lock exists to prevent stop() calls from being ignored if called when
242240
# _execute_phase_once is setting up the next phase thread.
243241
self._current_phase_thread_lock = threading.Lock()
@@ -267,8 +265,7 @@ def execute_phase(
267265
requested and successfully ran for this phase execution.
268266
"""
269267
repeat_count = 1
270-
repeat_limit = (phase.options.repeat_limit or
271-
DEFAULT_RETRIES)
268+
repeat_limit = (phase.options.repeat_limit or DEFAULT_RETRIES)
272269
while not self._stopping.is_set():
273270
is_last_repeat = repeat_count >= repeat_limit
274271
phase_execution_outcome, profile_stats = self._execute_phase_once(
@@ -277,9 +274,8 @@ def execute_phase(
277274
# Give 3 default retries for timeout phase.
278275
# Force repeat up to the repeat limit if force_repeat is set.
279276
if ((phase_execution_outcome.is_timeout and
280-
phase.options.repeat_on_timeout) or
281-
phase_execution_outcome.is_repeat or
282-
phase.options.force_repeat) and not is_last_repeat:
277+
phase.options.repeat_on_timeout) or phase_execution_outcome.is_repeat
278+
or phase.options.force_repeat) and not is_last_repeat:
283279
repeat_count += 1
284280
continue
285281

@@ -297,18 +293,18 @@ def _execute_phase_once(
297293
"""Executes the given phase, returning a PhaseExecutionOutcome."""
298294
# Check this before we create a PhaseState and PhaseRecord.
299295
if phase_desc.options.run_if and not phase_desc.options.run_if():
300-
_LOG.debug('Phase %s skipped due to run_if returning falsey.',
301-
phase_desc.name)
296+
self.logger.debug('Phase %s skipped due to run_if returning falsey.',
297+
phase_desc.name)
302298
return PhaseExecutionOutcome(phase_descriptor.PhaseResult.SKIP), None
303299

304300
override_result = None
305301
with self.test_state.running_phase_context(phase_desc) as phase_state:
306302
if subtest_rec:
307-
_LOG.debug('Executing phase %s under subtest %s', phase_desc.name,
308-
subtest_rec.name)
303+
self.logger.debug('Executing phase %s under subtest %s',
304+
phase_desc.name, subtest_rec.name)
309305
phase_state.set_subtest_name(subtest_rec.name)
310306
else:
311-
_LOG.debug('Executing phase %s', phase_desc.name)
307+
self.logger.debug('Executing phase %s', phase_desc.name)
312308
with self._current_phase_thread_lock:
313309
# Checking _stopping must be in the lock context, otherwise there is a
314310
# race condition: this thread checks _stopping and then switches to
@@ -328,7 +324,7 @@ def _execute_phase_once(
328324

329325
phase_state.result = phase_thread.join_or_die()
330326
if phase_state.result.is_repeat and is_last_repeat:
331-
_LOG.error('Phase returned REPEAT, exceeding repeat_limit.')
327+
self.logger.error('Phase returned REPEAT, exceeding repeat_limit.')
332328
phase_state.hit_repeat_limit = True
333329
override_result = PhaseExecutionOutcome(
334330
phase_descriptor.PhaseResult.STOP)
@@ -337,15 +333,15 @@ def _execute_phase_once(
337333
# Refresh the result in case a validation for a partially set measurement
338334
# or phase diagnoser raised an exception.
339335
result = override_result or phase_state.result
340-
_LOG.debug('Phase %s finished with result %s', phase_desc.name,
341-
result.phase_result)
336+
self.logger.debug('Phase %s finished with result %s', phase_desc.name,
337+
result.phase_result)
342338
return (result,
343339
phase_thread.get_profile_stats() if run_with_profiling else None)
344340

345341
def skip_phase(self, phase_desc: phase_descriptor.PhaseDescriptor,
346342
subtest_rec: Optional[test_record.SubtestRecord]) -> None:
347343
"""Skip a phase, but log a record of it."""
348-
_LOG.debug('Automatically skipping phase %s', phase_desc.name)
344+
self.logger.debug('Automatically skipping phase %s', phase_desc.name)
349345
with self.test_state.running_phase_context(phase_desc) as phase_state:
350346
if subtest_rec:
351347
phase_state.set_subtest_name(subtest_rec.name)
@@ -359,16 +355,16 @@ def evaluate_checkpoint(
359355
"""Evaluate a checkpoint, returning a PhaseExecutionOutcome."""
360356
if subtest_rec:
361357
subtest_name = subtest_rec.name
362-
_LOG.debug('Evaluating checkpoint %s under subtest %s', checkpoint.name,
363-
subtest_name)
358+
self.logger.debug('Evaluating checkpoint %s under subtest %s',
359+
checkpoint.name, subtest_name)
364360
else:
365-
_LOG.debug('Evaluating checkpoint %s', checkpoint.name)
361+
self.logger.debug('Evaluating checkpoint %s', checkpoint.name)
366362
subtest_name = None
367363
evaluated_millis = util.time_millis()
368364
try:
369365
outcome = PhaseExecutionOutcome(checkpoint.get_result(self.test_state))
370-
_LOG.debug('Checkpoint %s result: %s', checkpoint.name,
371-
outcome.phase_result)
366+
self.logger.debug('Checkpoint %s result: %s', checkpoint.name,
367+
outcome.phase_result)
372368
if outcome.is_fail_subtest and not subtest_rec:
373369
raise InvalidPhaseResultError(
374370
'Checkpoint returned FAIL_SUBTEST, but subtest not running.')
@@ -385,7 +381,7 @@ def evaluate_checkpoint(
385381
def skip_checkpoint(self, checkpoint: phase_branches.Checkpoint,
386382
subtest_rec: Optional[test_record.SubtestRecord]) -> None:
387383
"""Skip a checkpoint, but log a record of it."""
388-
_LOG.debug('Automatically skipping checkpoint %s', checkpoint.name)
384+
self.logger.debug('Automatically skipping checkpoint %s', checkpoint.name)
389385
subtest_name = subtest_rec.name if subtest_rec else None
390386
checkpoint_rec = test_record.CheckpointRecord.from_checkpoint(
391387
checkpoint, subtest_name,
@@ -417,11 +413,11 @@ def stop(
417413
if phase_thread.is_alive():
418414
phase_thread.kill()
419415

420-
_LOG.debug('Waiting for cancelled phase to exit: %s', phase_thread)
416+
self.logger.debug('Waiting for cancelled phase to exit: %s', phase_thread)
421417
timeout = timeouts.PolledTimeout.from_seconds(timeout_s)
422418
while phase_thread.is_alive() and not timeout.has_expired():
423419
time.sleep(0.1)
424-
_LOG.debug('Cancelled phase %s exit',
425-
"didn't" if phase_thread.is_alive() else 'did')
420+
self.logger.debug('Cancelled phase %s exit',
421+
"didn't" if phase_thread.is_alive() else 'did')
426422
# Clear the currently running phase, whether it finished or timed out.
427423
self.test_state.stop_running_phase()

openhtf/core/test_descriptor.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,8 @@ def abort_from_sig_int(self) -> None:
275275
self._executor.abort()
276276

277277
def execute(self,
278-
test_start: Optional[Union[phase_descriptor.PhaseT, Callable[[], str]]] = None,
278+
test_start: Optional[Union[phase_descriptor.PhaseT,
279+
Callable[[], str]]] = None,
279280
profile_filename: Optional[Text] = None) -> bool:
280281
"""Starts the framework and executes the given test.
281282
@@ -480,8 +481,8 @@ class TestApi(object):
480481
test_record: A reference to the output TestRecord for the currently running
481482
openhtf.Test. Direct access to this attribute is *strongly* discouraged,
482483
but provided as a catch-all for interfaces not otherwise provided by
483-
TestApi. If you find yourself using this, please file a
484-
feature request for an alternative at:
484+
TestApi. If you find yourself using this, please file a feature request
485+
for an alternative at:
485486
https://github.com/google/openhtf/issues/new
486487
"""
487488

@@ -583,8 +584,7 @@ def get_measurement(
583584
return self._running_test_state.get_measurement(measurement_name)
584585

585586
def get_measurement_strict(
586-
self,
587-
measurement_name: Text) -> test_state.ImmutableMeasurement:
587+
self, measurement_name: Text) -> test_state.ImmutableMeasurement:
588588
"""Get a copy of the test measurement from current or previous phase.
589589
590590
Measurement and phase name uniqueness is not enforced, so this method will

openhtf/util/configuration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
3131
CONF = configuration.CONF
3232
33-
ANITIMATTER_INTERMIX_CONSTANT = CONF.declare('antimatter_intermix_constant',
33+
ANTIMATTER_INTERMIX_CONSTANT = CONF.declare('antimatter_intermix_constant',
3434
default_value=3.14159,
3535
description='Intermix constant calibrated for our warp core.')
3636

openhtf/util/text.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,14 @@ def StringFromMeasurement(measurement: openhtf.Measurement,
130130
return _ColorText(text, _BRIGHT_RED_STYLE) if colorize_text else text
131131
elif measurement.outcome == measurements.Outcome.FAIL:
132132
text = (f'| {measurement.name} failed because '
133-
f'{measurement.measured_value.value} failed these checks: '
134-
'{}'.format([str(v) for v in measurement.validators]))
133+
f'{measurement.measured_value.value} failed these checks: ' +
134+
str([str(v) for v in measurement.validators]))
135135
return _ColorText(text, _BRIGHT_RED_STYLE) if colorize_text else text
136136
elif measurement.marginal:
137-
text = (f'| {measurement.name} is marginal because '
138-
f'{measurement.measured_value.value} is marginal in these checks: '
139-
'{}'.format([str(v) for v in measurement.validators]))
137+
text = (
138+
f'| {measurement.name} is marginal because '
139+
f'{measurement.measured_value.value} is marginal in these checks: ' +
140+
str([str(v) for v in measurement.validators]))
140141
return (_ColorText(text, str(colorama.Fore.YELLOW))
141142
if colorize_text else text)
142143
return f'| {measurement.name}: {measurement.measured_value.value}'

openhtf/util/threads.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,15 @@ class is meant to be subclassed. If you were to invoke this with
7979
during garbage collection.
8080
"""
8181

82-
def __init__(self, *args, **kwargs):
82+
def __init__(self, *args, logger: logging.Logger = _LOG, **kwargs):
8383
"""Initializer for KillableThread.
8484
8585
The keyword argument `run_with_profiling` is extracted from kwargs. If
8686
True, run this thread with profiling data collection.
8787
8888
Args:
8989
*args: Passed to the base class.
90+
logger: A logger for this class to use.
9091
**kwargs: Passed to the base class.
9192
"""
9293
self._run_with_profiling = kwargs.pop('run_with_profiling',
@@ -98,6 +99,7 @@ def __init__(self, *args, **kwargs):
9899
self._profiler = cProfile.Profile()
99100
else:
100101
self._profiler = None
102+
self._logger = logger
101103

102104
def run(self):
103105
try:
@@ -109,11 +111,11 @@ def run(self):
109111
self._thread_proc()
110112
except Exception: # pylint: disable=broad-except
111113
if not self._thread_exception(*sys.exc_info()):
112-
_LOG.critical('Thread raised an exception: %s', self.name)
114+
self._logger.critical('Thread raised an exception: %s', self.name)
113115
raise
114116
finally:
115117
self._thread_finished()
116-
_LOG.debug('Thread finished: %s', self.name)
118+
self._logger.debug('Thread finished: %s', self.name)
117119
if self._profiler is not None:
118120
self._profiler.disable()
119121

@@ -161,11 +163,11 @@ def kill(self):
161163
"""Terminates the current thread by raising an error."""
162164
self._killed.set()
163165
if not self.is_alive():
164-
logging.debug('Cannot kill thread that is no longer running.')
166+
self._logger.debug('Cannot kill thread that is no longer running.')
165167
return
166168
if not self._is_thread_proc_running():
167-
logging.debug("Thread's _thread_proc function is no longer running, "
168-
'will not kill; letting thread exit gracefully.')
169+
self._logger.debug("Thread's _thread_proc function is no longer running, "
170+
'will not kill; letting thread exit gracefully.')
169171
return
170172
self.async_raise(ThreadTerminationError)
171173

@@ -176,8 +178,8 @@ def async_raise(self, exc_type):
176178

177179
# If the thread has died we don't want to raise an exception so log.
178180
if not self.is_alive():
179-
_LOG.debug('Not raising %s because thread %s (%s) is not alive', exc_type,
180-
self.name, self.ident)
181+
self._logger.debug('Not raising %s because thread %s (%s) is not alive',
182+
exc_type, self.name, self.ident)
181183
return
182184

183185
result = ctypes.pythonapi.PyThreadState_SetAsyncExc(

test/core/exe_test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ def test_test_executor(self):
258258
def test_class_string(self):
259259
check_list = ['PhaseExecutorThread', 'phase_one']
260260
mock_test_state = mock.create_autospec(test_state.TestState)
261+
mock_test_state.state_logger = logging.getLogger(__name__)
261262
phase_thread = phase_executor.PhaseExecutorThread(
262263
phase_one, mock_test_state, run_with_profiling=False, subtest_rec=None)
263264
name = str(phase_thread)

test/output/callbacks/callbacks_test.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -181,21 +181,23 @@ def test_outcome_colors(self):
181181
def test_empty_outcome(self):
182182
"""Console Summary must not crash if phases have been skipped."""
183183
checkpoint = phase_branches.PhaseFailureCheckpoint.all_previous(
184-
"cp", action=phase_descriptor.PhaseResult.FAIL_SUBTEST)
184+
'cp', action=phase_descriptor.PhaseResult.FAIL_SUBTEST)
185185
phasegroup = phase_group.PhaseGroup(
186-
lambda: htf.PhaseResult.FAIL_AND_CONTINUE,
187-
lambda: htf.PhaseResult.SKIP,
188-
checkpoint,
186+
lambda: htf.PhaseResult.FAIL_AND_CONTINUE,
187+
lambda: htf.PhaseResult.SKIP,
188+
checkpoint,
189189
)
190-
subtest = phase_collections.Subtest("st", phasegroup)
191-
test = htf.Test(subtest)
190+
subtest = phase_collections.Subtest('st', phasegroup)
191+
test_instance = htf.Test(subtest)
192192

193193
result_store = util.NonLocalResult()
194+
194195
def _save_result(test_record):
195196
result_store.result = test_record
196-
test.add_output_callbacks(
197-
console_summary.ConsoleSummary(), _save_result)
198197

199-
test.execute()
200-
assert not any("Traceback" in record.message
198+
test_instance.add_output_callbacks(console_summary.ConsoleSummary(),
199+
_save_result)
200+
201+
test_instance.execute()
202+
assert not any('Traceback' in record.message
201203
for record in result_store.result.log_records)

test/util/text_test.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from openhtf.util import test
3131
from openhtf.util import text
3232
from openhtf.util import threads
33+
from openhtf.util import validators
3334

3435
# colorama makes these strings at runtime but pytype cannot infer this.
3536
_RED = typing.cast(str, colorama.Fore.RED)
@@ -148,6 +149,47 @@ def testStringFromMeasurement_SuccessfullyConvertsFailMeasurement(self):
148149
"| text_measurement_a failed because 5 failed these checks: ['x <= 3']")
149150
self.assertNotIn(text._BRIGHT_RED_STYLE, output)
150151

152+
def testStringFromMeasurement_SuccessfullyConvertsFailBracketedMeasurement(
153+
self):
154+
measurement = openhtf.Measurement('text_measurement_a').equals('hello')
155+
measurement._measured_value = measurements.MeasuredValue(
156+
'text_measurement_a')
157+
measurement._measured_value.set('{hi}')
158+
measurement.notify_value_set()
159+
output = text.StringFromMeasurement(measurement)
160+
self.assertEqual(output,
161+
('| text_measurement_a failed because {hi} '
162+
"failed these checks: [\"'x' matches /^hello$/\"]"))
163+
self.assertNotIn(text._BRIGHT_RED_STYLE, output)
164+
165+
def testStringFromMeasurement_SuccessfullyConvertsMarginalBracketedMeasurement(
166+
self):
167+
168+
class LengthInRange(validators.InRange):
169+
170+
def __call__(self, value):
171+
return super().__call__(len(value))
172+
173+
def is_marginal(self, value) -> bool:
174+
return super().is_marginal(len(value))
175+
176+
# Length of 5 is pass, 4 or 6 is marginal.
177+
validator = LengthInRange(
178+
minimum=4, maximum=6, marginal_minimum=5, marginal_maximum=5)
179+
180+
measurement = openhtf.Measurement('text_measurement_a').with_validator(
181+
validator)
182+
measurement._measured_value = measurements.MeasuredValue(
183+
'text_measurement_a')
184+
measurement._measured_value.set('{hi}')
185+
measurement.notify_value_set()
186+
output = text.StringFromMeasurement(measurement)
187+
self.assertEqual(
188+
output,
189+
('| text_measurement_a is marginal because {hi} is marginal '
190+
"in these checks: ['4 <= Marginal:5 <= x <= Marginal:5 <= 6']"))
191+
self.assertNotIn(text._BRIGHT_RED_STYLE, output)
192+
151193
def testStringFromMeasurement_SuccessfullyConvertsFailMeasurementColorized(
152194
self):
153195
measurement = openhtf.Measurement('text_measurement_a').in_range(maximum=3)

0 commit comments

Comments
 (0)