def _execute_phase_once( self, phase_desc: phase_descriptor.PhaseDescriptor, is_last_repeat: bool, run_with_profiling: bool, subtest_rec: Optional[test_record.SubtestRecord], ) -> Tuple[PhaseExecutionOutcome, Optional[pstats.Stats]]: """Executes the given phase, returning a PhaseExecutionOutcome.""" # Check this before we create a PhaseState and PhaseRecord. if phase_desc.options.run_if and not phase_desc.options.run_if(): _LOG.debug('Phase %s skipped due to run_if returning falsey.', phase_desc.name) return PhaseExecutionOutcome( phase_descriptor.PhaseResult.SKIP), None override_result = None with self.test_state.running_phase_context(phase_desc) as phase_state: if subtest_rec: _LOG.debug('Executing phase %s under subtest %s', phase_desc.name, subtest_rec.name) phase_state.set_subtest_name(subtest_rec.name) else: _LOG.debug('Executing phase %s', phase_desc.name) with self._current_phase_thread_lock: # Checking _stopping must be in the lock context, otherwise there is a # race condition: this thread checks _stopping and then switches to # another thread where stop() sets _stopping and checks # _current_phase_thread (which would not be set yet). In that case, the # new phase thread will be still be started. if self._stopping.is_set(): # PhaseRecord will be written at this point, so ensure that it has a # Killed result. result = PhaseExecutionOutcome( threads.ThreadTerminationError()) phase_state.result = result return result, None phase_thread = PhaseExecutorThread(phase_desc, self.test_state, run_with_profiling, subtest_rec) phase_thread.start() self._current_phase_thread = phase_thread phase_state.result = phase_thread.join_or_die() if phase_state.result.is_repeat and is_last_repeat: _LOG.error('Phase returned REPEAT, exceeding repeat_limit.') phase_state.hit_repeat_limit = True override_result = PhaseExecutionOutcome( phase_descriptor.PhaseResult.STOP) self._current_phase_thread = None # Refresh the result in case a validation for a partially set measurement # or phase diagnoser raised an exception. result = override_result or phase_state.result _LOG.debug('Phase %s finished with result %s', phase_desc.name, result.phase_result) return (result, phase_thread.get_profile_stats() if run_with_profiling else None)
def JoinOrDie(self): """Wait for thread to finish, return a PhaseOutcome with its response.""" if self._phase.options.timeout_s is not None: self.join(self._phase.options.timeout_s) else: self.join(DEFAULT_PHASE_TIMEOUT_S) # We got a return value or an exception and handled it. if isinstance(self._phase_outcome, PhaseOutcome): return self._phase_outcome # Check for timeout, indicated by None for PhaseOutcome.phase_result. if self.is_alive(): self.Kill() return PhaseOutcome(None) # Phase was killed. return PhaseOutcome(threads.ThreadTerminationError())
def join_or_die(self): """Wait for thread to finish, returning a PhaseExecutionOutcome instance.""" if self._phase_desc.options.timeout_s is not None: self.join(self._phase_desc.options.timeout_s) else: self.join(DEFAULT_PHASE_TIMEOUT_S) # We got a return value or an exception and handled it. if isinstance(self._phase_execution_outcome, PhaseExecutionOutcome): return self._phase_execution_outcome # Check for timeout, indicated by None for # PhaseExecutionOutcome.phase_result. if self.is_alive(): self.kill() return PhaseExecutionOutcome(None) # Phase was killed. return PhaseExecutionOutcome(threads.ThreadTerminationError())
def _execute_phase_once(self, phase_desc, is_last_repeat): """Executes the given phase, returning a PhaseExecutionOutcome.""" # Check this before we create a PhaseState and PhaseRecord. if phase_desc.options.run_if and not phase_desc.options.run_if(): _LOG.debug('Phase %s skipped due to run_if returning falsey.', phase_desc.name) return PhaseExecutionOutcome(openhtf.PhaseResult.SKIP) with self.test_state.running_phase_context(phase_desc) as phase_state: _LOG.debug('Executing phase %s', phase_desc.name) with self._current_phase_thread_lock: # Checking _stopping must be in the lock context, otherwise there is a # race condition: this thread checks _stopping and then switches to # another thread where stop() sets _stopping and checks # _current_phase_thread (which would not be set yet). In that case, the # new phase thread will be still be started. if self._stopping.is_set(): # PhaseRecord will be written at this point, so ensure that it has a # Killed result. result = PhaseExecutionOutcome( threads.ThreadTerminationError()) phase_state.result = result return result phase_thread = PhaseExecutorThread(phase_desc, self.test_state) phase_thread.start() self._current_phase_thread = phase_thread result = phase_state.result = phase_thread.join_or_die() if phase_state.result.is_repeat and is_last_repeat: _LOG.error('Phase returned REPEAT, exceeding repeat_limit.') phase_state.hit_repeat_limit = True result = PhaseExecutionOutcome(openhtf.PhaseResult.STOP) self._current_phase_thread = None _LOG.debug('Phase %s finished with result %s', phase_desc.name, result.phase_result) return result
def JoinOrDie(self): """Wait for thread to finish, return a TestPhaseResult with its response.""" if self._phase.options.timeout_s is not None: self.join(self._phase.options.timeout_s) else: self.join(FLAGS.phase_default_timeout_ms / 1000.0) if isinstance(self._phase_result, TestPhaseResult): return self._phase_result if self.is_alive(): # Timeout self.Kill() return self._MakePhaseResult(phase_data.PhaseResults.TIMEOUT) if self._phase_result is None: # Finished with no return value, assume continue. return self._MakePhaseResult(phase_data.PhaseResults.CONTINUE) if self._phase_result is DIDNT_FINISH: # Phase was killed return self._MakePhaseResult(threads.ThreadTerminationError()) return self._MakePhaseResult(self._phase_result)
def join_or_die(self) -> PhaseExecutionOutcome: """Wait for thread to finish, returning a PhaseExecutionOutcome instance.""" deadline = time.monotonic() + DEFAULT_PHASE_TIMEOUT_S if self._phase_desc.options.timeout_s is not None: deadline = time.monotonic() + self._phase_desc.options.timeout_s while time.monotonic() < deadline: # Using exception to kill thread is not honored when thread is busy, # so we leave the thread behind, and move on teardown. self.join(_JOIN_TRY_INTERVAL_SECONDS) if not self.is_alive() or self._killed.is_set(): break # We got a return value or an exception and handled it. if self._phase_execution_outcome: return self._phase_execution_outcome # Check for timeout, indicated by None for # PhaseExecutionOutcome.phase_result. if self.is_alive(): self.kill() return PhaseExecutionOutcome(None) # Phase was killed. return PhaseExecutionOutcome(threads.ThreadTerminationError())
class TextTest(test.TestCase, parameterized.TestCase): def testColorFromTestOutcome_HasCorrespondingTestOutcomeName(self): """Catches OpenHTF test outcome not added in _ColorFromTestOutcome.""" for member in test_record.Outcome.__members__: self.assertIn(member, text._ColorFromTestOutcome.__members__) def testHeadlineFromTestOutcome_HasCorrespondingTestOutcomeName(self): """Catches OpenHTF test outcome not added in _HeadlineFromTestOutcome.""" for member in test_record.Outcome.__members__: self.assertIn(member, text._HeadlineFromTestOutcome.__members__) def testColorText_GetsColorSuccessfully(self): text_to_colorize = 'Foo Bar' self.assertEqual( text._ColorText(text_to_colorize, _GREEN), f'{_GREEN}{text_to_colorize}{colorama.Style.RESET_ALL}') # TODO(b/70517332): Pytype currently doesn't properly support the functional # API of enums: https://github.com/google/pytype/issues/459. Remove # disabling pytype once fixed. @parameterized.named_parameters( (headline_member.name, headline_member.name, headline_member.value) for headline_member in text._HeadlineFromTestOutcome.__iter__()) # pytype: disable=attribute-error def testGetTestOutcomeHeadline_TestNotColorized(self, outcome, headline): record = test_record.TestRecord(dut_id='TestDutId', station_id='test_station', outcome=test_record.Outcome[outcome]) self.assertEqual(text._GetTestOutcomeHeadline(record), headline) # TODO(b/70517332): Pytype currently doesn't properly support the functional # API of enums: https://github.com/google/pytype/issues/459. Remove # disabling pytype once fixed. @parameterized.named_parameters( (headline_member.name, headline_member.name, headline_member.value) for headline_member in text._HeadlineFromTestOutcome.__iter__()) # pytype: disable=attribute-error def testGetTestOutcomeHeadline_TestColorized(self, outcome, headline): record = test_record.TestRecord(dut_id='TestDutId', station_id='test_station', outcome=test_record.Outcome[outcome]) # TODO(b/70517332): Pytype currently doesn't properly support the functional # API of enums: https://github.com/google/pytype/issues/459. Remove # disabling pytype once fixed. self.assertEqual( text._GetTestOutcomeHeadline(record, colorize_text=True), f'{text._ColorFromTestOutcome[outcome].value}{headline}' # pytype: disable=unsupported-operands f'{colorama.Style.RESET_ALL}') def testStringFromMeasurement_SuccessfullyConvertsUnsetMeasurement(self): self.assertEqual( text.StringFromMeasurement( openhtf.Measurement('text_measurement_a')), '| text_measurement_a was not set') def testStringFromMeasurement_SuccessfullyConvertsPassMeasurement(self): measurement = openhtf.Measurement('text_measurement_a') measurement._measured_value = measurements.MeasuredValue( 'text_measurement_a') measurement._measured_value.set(10) measurement.notify_value_set() self.assertEqual(text.StringFromMeasurement(measurement), '| text_measurement_a: 10') def testStringFromMeasurement_SuccessfullyConvertsFailMeasurement(self): measurement = openhtf.Measurement('text_measurement_a').in_range( maximum=3) measurement._measured_value = measurements.MeasuredValue( 'text_measurement_a') measurement._measured_value.set(5) measurement.notify_value_set() output = text.StringFromMeasurement(measurement) self.assertEqual( output, "| text_measurement_a failed because 5 failed these checks: ['x <= 3']" ) self.assertNotIn(text._BRIGHT_RED_STYLE, output) def testStringFromMeasurement_SuccessfullyConvertsFailMeasurementColorized( self): measurement = openhtf.Measurement('text_measurement_a').in_range( maximum=3) measurement._measured_value = measurements.MeasuredValue( 'text_measurement_a') measurement._measured_value.set(5) measurement.notify_value_set() self.assertEqual( text.StringFromMeasurement(measurement, colorize_text=True).count( text._BRIGHT_RED_STYLE), 1) def testStringFromAttachment_SuccessfullyConvertsPassMeasurement(self): attachment = test_record.Attachment('content', 'text/plain') self.assertEqual( text.StringFromAttachment(attachment, 'attachment_a.txt'), '| attachment: attachment_a.txt (mimetype=text/plain)') @parameterized.named_parameters([ { 'testcase_name': 'None', 'phase_result': None, 'expected_str': '' }, { 'testcase_name': 'PhaseResult', 'phase_result': phase_descriptor.PhaseResult.CONTINUE, 'expected_str': 'CONTINUE' }, { 'testcase_name': 'ExceptionInfo', 'phase_result': phase_executor.ExceptionInfo( ValueError, ValueError('Invalid Value'), mock.create_autospec(types.TracebackType, spec_set=True)), 'expected_str': 'ValueError' }, { 'testcase_name': 'ThreadTerminationError', 'phase_result': threads.ThreadTerminationError(), 'expected_str': 'ThreadTerminationError' }, ]) def testStringFromPhaseExecutionOutcome_SuccessfullyConvertsOutcome( self, phase_result, expected_str): self.assertEqual( text.StringFromPhaseExecutionOutcome( phase_executor.PhaseExecutionOutcome(phase_result)), expected_str) def testStringFromPhaseRecord_SuccessfullyConvertsPhaseRecordPassPhase( self): record = self.execute_phase_or_test(PhaseThatSucceeds) output = text.StringFromPhaseRecord(record) self.assertEqual( output, 'Phase PhaseThatSucceeds\n' '+ Outcome: PASS Result: CONTINUE\n' '| text_measurement_a: a\n' '| text_measurement_b: b\n' '| attachment: attachment_a.txt (mimetype=text/plain)\n' '| attachment: attachment_b.json (mimetype=application/json)') self.assertNotIn(text._BRIGHT_RED_STYLE, output) def testStringFromPhaseRecord_SuccessfullyConvertsPhaseRecordFailPhase( self): record = self.execute_phase_or_test(PhaseWithFailure) output = text.StringFromPhaseRecord(record) self.assertEqual( output, 'Phase PhaseWithFailure\n' '+ Outcome: FAIL Result: CONTINUE\n' '| text_measurement_a failed because intentionally wrong measurement ' 'failed these checks: ["\'x\' matches /^a$/"]\n' '| text_measurement_b was not set\n' '| text_measurement_c: c') def testStringFromPhaseRecord_SuccessfullyConvertsPhaseFailLimitPhase( self): record = self.execute_phase_or_test(PhaseWithFailure) output = text.StringFromPhaseRecord(record, maximum_num_measurements=2) self.assertEqual( output, 'Phase PhaseWithFailure\n' '+ Outcome: FAIL Result: CONTINUE\n' '| text_measurement_a failed because intentionally wrong measurement ' 'failed these checks: ["\'x\' matches /^a$/"]\n' '| text_measurement_b was not set\n' '...') def testStringFromPhaseRecord_SuccessfullyConvertsPhaseRecordOnlyFailPhase( self): record = self.execute_phase_or_test(PhaseWithFailure) output = text.StringFromPhaseRecord(record, only_failures=True) self.assertEqual( output, 'Phase PhaseWithFailure\n' '+ Outcome: FAIL Result: CONTINUE\n' '| text_measurement_a failed because intentionally wrong measurement ' 'failed these checks: ["\'x\' matches /^a$/"]') def testStringFromPhaseRecord_SuccessfullyConvertsPhaseRecordFailPhaseColored( self): record = self.execute_phase_or_test(PhaseWithFailure) self.assertEqual( text.StringFromPhaseRecord(record, colorize_text=True).count(_RED), 3) def testStringFromPhaseRecord_SuccessfullyConvertsPhaseRecordSkipPhaseColored( self): record = self.execute_phase_or_test(PhaseWithSkip) self.assertNotIn( text._BRIGHT_RED_STYLE, text.StringFromPhaseRecord(record, colorize_text=True)) @parameterized.named_parameters([ { 'testcase_name': 'OneOutcome', 'outcome_details': [ test_record.OutcomeDetails(code=1, description='Unknown exception.') ], 'expected_str': ('The test thinks this may be the reason:\n' '1: Unknown exception.'), }, { 'testcase_name': 'TwoOutcomes', 'outcome_details': [ test_record.OutcomeDetails(code=1, description='Unknown exception.'), test_record.OutcomeDetails(code='FooError', description='Foo exception.') ], 'expected_str': ('The test thinks these may be the reason:\n' '1: Unknown exception.\n' 'FooError: Foo exception.'), }, ]) def testStringFromOutcomeDetails_SuccessfullyConvertsOutcomeDetails( self, outcome_details, expected_str): self.assertEqual(text.StringFromOutcomeDetails(outcome_details), expected_str) def testStringFromTestRecord_SuccessfullyConvertsTestRecordSinglePassPhase( self): record = self.execute_phase_or_test(openhtf.Test(PhaseThatSucceeds)) self.assertEqual( text.StringFromTestRecord(record), 'Test finished with a PASS!\n' 'Woohoo!\n' 'Phase trigger_phase\n' '+ Outcome: PASS Result: CONTINUE\n' 'Phase PhaseThatSucceeds\n' '+ Outcome: PASS Result: CONTINUE\n' '| text_measurement_a: a\n' '| text_measurement_b: b\n' '| attachment: attachment_a.txt (mimetype=text/plain)\n' '| attachment: attachment_b.json (mimetype=application/json)\n' 'Test finished with a PASS!') def testStringFromTestRecord_SuccessfullyConvertsTestErrorPhase(self): record = self.execute_phase_or_test(openhtf.Test(PhaseWithError)) self.assertEqual( text.StringFromTestRecord(record), 'Test encountered an ERROR!!!\n' 'Phase trigger_phase\n' '+ Outcome: PASS Result: CONTINUE\n' 'Phase PhaseWithError\n' '+ Outcome: ERROR Result: Exception\n' '| text_measurement_a was not set\n' '| text_measurement_b was not set\n' 'The test thinks this may be the reason:\n' 'Exception: Intentional exception from test case.\n' 'Test encountered an ERROR!!!') def testStringFromTestRecord_SuccessfullyConvertsTestFailurePhase(self): record = self.execute_phase_or_test(openhtf.Test(PhaseWithFailure)) output = text.StringFromTestRecord(record) self.assertEqual( output, 'Test finished with a FAIL :(\n' 'Phase trigger_phase\n' '+ Outcome: PASS Result: CONTINUE\n' 'Phase PhaseWithFailure\n' '+ Outcome: FAIL Result: CONTINUE\n' '| text_measurement_a failed because intentionally wrong measurement ' 'failed these checks: ["\'x\' matches /^a$/"]\n' '| text_measurement_b was not set\n' '| text_measurement_c: c\n' 'Test finished with a FAIL :(') self.assertNotIn(text._BRIGHT_RED_STYLE, output) def testStringFromTestRecord_SuccessfullyConvertsTestOnlyFailurePhase( self): record = self.execute_phase_or_test( openhtf.Test(PhaseThatSucceeds, PhaseWithFailure)) output = text.StringFromTestRecord(record, only_failures=True) self.assertEqual( output, 'Test finished with a FAIL :(\n' 'Phase PhaseWithFailure\n' '+ Outcome: FAIL Result: CONTINUE\n' '| text_measurement_a failed because intentionally wrong measurement ' 'failed these checks: ["\'x\' matches /^a$/"]\n' 'Test finished with a FAIL :(') self.assertNotIn(text._BRIGHT_RED_STYLE, output) def testStringFromTestRecord_SuccessfullyConvertsTestFailurePhaseColored( self): record = self.execute_phase_or_test(openhtf.Test(PhaseWithFailure)) self.assertEqual( text.StringFromTestRecord(record, colorize_text=True).count(_RED), 5) def testStringFromTestRecord_SuccessfullyConvertsTestFailureMultiplePhases( self): record = self.execute_phase_or_test( openhtf.Test(PhaseThatSucceeds, PhaseWithFailure)) self.assertEqual( text.StringFromTestRecord(record), 'Test finished with a FAIL :(\n' 'Phase trigger_phase\n' '+ Outcome: PASS Result: CONTINUE\n' 'Phase PhaseThatSucceeds\n' '+ Outcome: PASS Result: CONTINUE\n' '| text_measurement_a: a\n' '| text_measurement_b: b\n' '| attachment: attachment_a.txt (mimetype=text/plain)\n' '| attachment: attachment_b.json (mimetype=application/json)\n' 'Phase PhaseWithFailure\n' '+ Outcome: FAIL Result: CONTINUE\n' '| text_measurement_a failed because intentionally wrong measurement ' 'failed these checks: ["\'x\' matches /^a$/"]\n' '| text_measurement_b was not set\n' '| text_measurement_c: c\n' 'Test finished with a FAIL :(') def testPrintTestRecord_SuccessfullyLogsNotColored(self): record = self.execute_phase_or_test(openhtf.Test(PhaseThatSucceeds)) with mock.patch.object(sys, 'stdout', new_callable=io.StringIO) as cm: with mock.patch.object(sys.stdout, sys.stdout.isatty.__name__, autospec=True, spec_set=True, return_value=False): text.PrintTestRecord(record) self.assertTrue(cm.getvalue()) self.assertNotIn(_GREEN, cm.getvalue()) def testPrintTestRecord_SuccessfullyLogsColored(self): record = self.execute_phase_or_test(openhtf.Test(PhaseThatSucceeds)) with mock.patch.object(sys, 'stdout', new_callable=io.StringIO) as cm: with mock.patch.object(sys.stdout, sys.stdout.isatty.__name__, autospec=True, spec_set=True, return_value=True): text.PrintTestRecord(record) self.assertIn(_GREEN, cm.getvalue())
class TestTestApi(parameterized.TestCase): def setUp(self): super(TestTestApi, self).setUp() patcher = mock.patch.object(test_record.PhaseRecord, 'record_start_time', return_value=11235) self.mock_record_start_time = patcher.start() self.addCleanup(patcher.stop) self.test_descriptor = test_descriptor.TestDescriptor( phase_collections.PhaseSequence((test_phase, )), test_record.CodeInfo.uncaptured(), {'config': {}}) self.test_state = test_state.TestState(self.test_descriptor, 'testing-123', test_descriptor.TestOptions()) self.test_record = self.test_state.test_record self.running_phase_state = test_state.PhaseState.from_descriptor( test_phase, self.test_state, logging.getLogger()) self.test_state.running_phase_state = self.running_phase_state self.test_api = self.test_state.test_api def test_get_attachment(self): attachment_name = 'attachment.txt' input_contents = b'This is some attachment text!' mimetype = 'text/plain' self.test_api.attach(attachment_name, input_contents, mimetype) output_attachment = self.test_api.get_attachment(attachment_name) if not output_attachment: # Need branch to appease pytype. self.fail('output_attachment not found') self.assertEqual(input_contents, output_attachment.data) self.assertEqual(mimetype, output_attachment.mimetype) def test_get_attachment_strict(self): attachment_name = 'attachment.txt' with self.assertRaises(test_descriptor.AttachmentNotFoundError): self.test_api.get_attachment_strict(attachment_name) def test_get_measurement(self): measurement_val = [1, 2, 3] self.test_api.measurements['test_measurement'] = measurement_val measurement = self.test_api.get_measurement('test_measurement') if not measurement: # Need branch to appease pytype. self.fail('measurement not found.') self.assertEqual(measurement_val, measurement.value) self.assertEqual('test_measurement', measurement.name) def test_get_measurement_immutable(self): measurement_val = [1, 2, 3] self.test_api.measurements['test_measurement'] = measurement_val measurement = self.test_api.get_measurement('test_measurement') if not measurement: # Need branch to appease pytype. self.fail('measurement not found.') self.assertEqual(measurement_val, measurement.value) self.assertEqual('test_measurement', measurement.name) measurement.value.append(4) self.assertNotEqual(measurement_val, measurement.value) def test_infer_mime_type_from_file_name(self): with tempfile.NamedTemporaryFile(suffix='.txt') as f: f.write(b'Mock text contents.') f.flush() file_name = f.name self.test_api.attach_from_file(file_name, 'attachment') attachment = self.test_api.get_attachment('attachment') if not attachment: # Need branch to appease pytype. self.fail('attachment not found.') self.assertEqual(attachment.mimetype, 'text/plain') def test_infer_mime_type_from_attachment_name(self): with tempfile.NamedTemporaryFile() as f: f.write(b'Mock text contents.') f.flush() file_name = f.name self.test_api.attach_from_file(file_name, 'attachment.png') attachment = self.test_api.get_attachment('attachment.png') if not attachment: # Need branch to appease pytype. self.fail('attachment not found.') self.assertEqual(attachment.mimetype, 'image/png') def test_phase_state_cache(self): basetypes = self.running_phase_state.as_base_types() expected_initial_basetypes = copy.deepcopy( PHASE_STATE_BASE_TYPE_INITIAL) expected_initial_basetypes['descriptor_id'] = basetypes[ 'descriptor_id'] self.assertEqual(expected_initial_basetypes, basetypes) self.assertFalse(self.running_phase_state._update_measurements) self.test_api.measurements.test_measurement = 5 self.assertEqual({'test_measurement'}, self.running_phase_state._update_measurements) self.running_phase_state.as_base_types() expected_after_basetypes = copy.deepcopy(expected_initial_basetypes) expected_after_basetypes['measurements']['test_measurement'].update({ 'outcome': 'PASS', 'measured_value': 5, }) self.assertEqual(expected_after_basetypes, basetypes) self.assertFalse(self.running_phase_state._update_measurements) def test_test_state_cache(self): basetypes = self.test_state.as_base_types() # The descriptor id is not static, so grab it. expected_initial_basetypes = copy.deepcopy( TEST_STATE_BASE_TYPE_INITIAL) descriptor_id = basetypes['running_phase_state']['descriptor_id'] expected_initial_basetypes['running_phase_state']['descriptor_id'] = ( descriptor_id) self.assertEqual(expected_initial_basetypes, basetypes) self.running_phase_state._finalize_measurements() self.test_record.add_phase_record( self.running_phase_state.phase_record) self.test_state.running_phase_state = None basetypes2 = self.test_state.as_base_types() expected_after_phase_record_basetypes = copy.deepcopy( PHASE_RECORD_BASE_TYPE) expected_after_phase_record_basetypes['descriptor_id'] = descriptor_id self.assertEqual(expected_after_phase_record_basetypes, basetypes2['test_record']['phases'][0]) self.assertIsNone(basetypes2['running_phase_state']) @parameterized.parameters( (phase_executor.PhaseExecutionOutcome(None), test_record.Outcome.TIMEOUT), (phase_executor.PhaseExecutionOutcome( phase_descriptor.PhaseResult.STOP), test_record.Outcome.FAIL), (phase_executor.PhaseExecutionOutcome( threads.ThreadTerminationError()), test_record.Outcome.ERROR)) def test_test_state_finalize_from_phase_outcome( self, phase_exe_outcome: phase_executor.PhaseExecutionOutcome, test_record_outcome: test_record.Outcome): self.test_state.finalize_from_phase_outcome(phase_exe_outcome) self.assertEqual(self.test_state.test_record.outcome, test_record_outcome) def test_test_state_finalize_from_phase_outcome_exception_info(self): try: raise ValueError('Exception for unit testing.') except ValueError: phase_exe_outcome = phase_executor.PhaseExecutionOutcome( phase_executor.ExceptionInfo(*sys.exc_info())) self.test_state.finalize_from_phase_outcome(phase_exe_outcome) self.assertEqual(self.test_state.test_record.outcome, test_record.Outcome.ERROR)