def describe(cls, exception, use_traceback=False, prefix=""): # -- NORMAL CASE: text = u"{prefix}{0}: {1}\n".format(exception.__class__.__name__, exception, prefix=prefix) if use_traceback: exc_traceback = cls.get_traceback(exception) if exc_traceback: # -- NOTE: Chained-exception cause (see: PEP-3134). text += u"".join(traceback.format_tb(exc_traceback)) return text
def check_traceback_format(self, cleanup_func=None): try: if issubclass(six.binary_type, six.string_types): # Python 2.6 or other platform where the interpreter # is likely going to be spitting out bytes, which will # then fail with io.StringIO(), so we skip the cross- # checks with the C API there. Note that _testcapi # is included in (at least) Ubuntu CPython packages, which # makes the import check less effective than desired. raise ImportError from _testcapi import traceback_print except ImportError: traceback_print = None try: self.some_exception() except KeyError: type_, value, tb = sys.exc_info() if cleanup_func is not None: # Clear the inner frames, not this one cleanup_func(tb.tb_next) traceback_fmt = u('Traceback (most recent call last):\n') + \ u('').join(traceback.format_tb(tb)) if traceback_print is not None: file_ = StringIO() traceback_print(tb, file_) python_fmt = file_.getvalue() # Call all _tb and _exc functions with captured_output("stderr") as tbstderr: traceback.print_tb(tb) tbfile = StringIO() traceback.print_tb(tb, file=tbfile) with captured_output("stderr") as excstderr: traceback.print_exc() excfmt = traceback.format_exc() excfile = StringIO() traceback.print_exc(file=excfile) else: self.fail("unable to create test traceback string") # Make sure that Python and the traceback module format the same thing if traceback_print is not None: self.assertEqual(traceback_fmt, python_fmt) # Now verify the _tb func output self.assertEqual(tbstderr.getvalue(), tbfile.getvalue()) # Now verify the _exc func output self.assertEqual(excstderr.getvalue(), excfile.getvalue()) self.assertEqual(excfmt, excfile.getvalue()) # Make sure that the traceback is properly indented. tb_lines = traceback_fmt.splitlines() self.assertEqual(len(tb_lines), 5) banner = tb_lines[0] location, source_line = tb_lines[-2:] self.assertTrue(banner.startswith('Traceback')) self.assertTrue(location.startswith(' File')) self.assertTrue(source_line.startswith(' raise'))
def execute_steps(self, steps_text): """The steps identified in the "steps" text string will be parsed and executed in turn just as though they were defined in a feature file. If the execute_steps call fails (either through error or failure assertion) then the step invoking it will need to catch the resulting exceptions. :param steps_text: Text with the Gherkin steps to execute (as string). :returns: True, if the steps executed successfully. :raises: AssertionError, if a step failure occurs. :raises: ValueError, if invoked without a feature context. """ assert isinstance(steps_text, six.text_type), "Steps must be unicode." if not self.feature: raise ValueError("execute_steps() called outside of feature") # -- PREPARE: Save original context data for current step. # Needed if step definition that called this method uses .table/.text original_table = getattr(self, "table", None) original_text = getattr(self, "text", None) self.feature.parser.variant = "steps" steps = self.feature.parser.parse_steps(steps_text) with self._use_with_behave_mode(): for step in steps: passed = step.run(self._runner, quiet=True, capture=False) if not passed: # -- ISSUE #96: Provide more substep info to diagnose problem. step_line = u"%s %s" % (step.keyword, step.name) message = "%s SUB-STEP: %s" % \ (step.status.name.upper(), step_line) if step.error_message: message += "\nSubstep info: %s\n" % step.error_message message += u"Traceback (of failed substep):\n" message += u"".join( traceback.format_tb(step.exc_traceback)) # message += u"\nTraceback (of context.execute_steps()):" assert False, message # -- FINALLY: Restore original context data for current step. self.table = original_table self.text = original_text return True
def execute_steps(self, steps_text): """The steps identified in the "steps" text string will be parsed and executed in turn just as though they were defined in a feature file. If the execute_steps call fails (either through error or failure assertion) then the step invoking it will need to catch the resulting exceptions. :param steps_text: Text with the Gherkin steps to execute (as string). :returns: True, if the steps executed successfully. :raises: AssertionError, if a step failure occurs. :raises: ValueError, if invoked without a feature context. """ assert isinstance(steps_text, six.text_type), "Steps must be unicode." if not self.feature: raise ValueError("execute_steps() called outside of feature") # -- PREPARE: Save original context data for current step. # Needed if step definition that called this method uses .table/.text original_table = getattr(self, "table", None) original_text = getattr(self, "text", None) self.feature.parser.variant = "steps" steps = self.feature.parser.parse_steps(steps_text) with self._use_with_behave_mode(): for step in steps: passed = step.run(self._runner, quiet=True, capture=False) if not passed: # -- ISSUE #96: Provide more substep info to diagnose problem. step_line = u"%s %s" % (step.keyword, step.name) message = "%s SUB-STEP: %s" % \ (step.status.name.upper(), step_line) if step.error_message: message += "\nSubstep info: %s\n" % step.error_message message += u"Traceback (of failed substep):\n" message += u"".join(traceback.format_tb(step.exc_traceback)) # message += u"\nTraceback (of context.execute_steps()):" assert False, message # -- FINALLY: Restore original context data for current step. self.table = original_table self.text = original_text return True
def _process_scenario(self, scenario, report): """Process a scenario and append information to JUnit report object. This corresponds to a JUnit testcase: * testcase.@classname = f(filename) +'.'+ feature.name * testcase.@name = scenario.name * testcase.@status = scenario.status * testcase.@time = scenario.duration Distinguishes now between failures and errors. Failures are AssertationErrors: expectation is violated/not met. Errors are unexpected RuntimeErrors (all other exceptions). If a failure/error occurs, the step, that caused the failure, and its location are provided now. :param scenario: Scenario to process. :param report: Context object to store/add info to (outgoing param). """ # pylint: disable=too-many-locals, too-many-branches, too-many-statements assert isinstance(scenario, Scenario) assert not isinstance(scenario, ScenarioOutline) if scenario.status != Status.skipped or self.show_skipped: # -- NOTE: Count only if not-skipped or skipped should be shown. report.counts_tests += 1 classname = report.classname feature = report.feature feature_name = feature.name if not feature_name: feature_name = self.make_feature_filename(feature) case = ElementTree.Element('testcase') case.set(u"classname", u"%s.%s" % (classname, feature_name)) case.set(u"name", scenario.name or "") case.set(u"status", scenario.status.name) case.set(u"time", _text(round(scenario.duration, 6))) step = None failing_step = None if scenario.status == Status.failed: for status in (Status.failed, Status.undefined): step = self.select_step_with_status(status, scenario) if step: break # -- NOTE: Scenario may fail now due to hook-errors. element_name = "failure" if step and isinstance(step.exception, (AssertionError, type(None))): # -- FAILURE: AssertionError assert step.status in (Status.failed, Status.undefined) report.counts_failed += 1 else: # -- UNEXPECTED RUNTIME-ERROR: report.counts_errors += 1 element_name = "error" # -- COMMON-PART: failure = ElementTree.Element(element_name) if step: step_text = self.describe_step(step).rstrip() text = u"\nFailing step: %s\nLocation: %s\n" % \ (step_text, step.location) message = _text(step.exception) failure.set(u'type', step.exception.__class__.__name__) failure.set(u'message', message) text += _text(step.error_message) else: # -- MAYBE: Hook failure before any step is executed. failure_type = "UnknownError" if scenario.exception: failure_type = scenario.exception.__class__.__name__ failure.set(u'type', failure_type) failure.set(u'message', scenario.error_message or "") traceback_lines = traceback.format_tb(scenario.exc_traceback) traceback_lines.insert(0, u"Traceback:\n") text = _text(u"".join(traceback_lines)) failure.append(CDATA(text)) case.append(failure) elif (scenario.status in (Status.skipped, Status.untested) and self.show_skipped): report.counts_skipped += 1 step = self.select_step_with_status(Status.undefined, scenario) if step: # -- UNDEFINED-STEP: report.counts_failed += 1 failure = ElementTree.Element(u"failure") failure.set(u"type", u"undefined") failure.set(u"message", (u"Undefined Step: %s" % step.name)) case.append(failure) else: skip = ElementTree.Element(u'skipped') case.append(skip) # Create stdout section for each test case stdout = ElementTree.Element(u"system-out") text = u"" if self.show_scenarios: text = self.describe_scenario(scenario) # Append the captured standard output if scenario.captured.stdout: output = _text(scenario.captured.stdout) text += u"\nCaptured stdout:\n%s\n" % output stdout.append(CDATA(text)) case.append(stdout) # Create stderr section for each test case if scenario.captured.stderr: stderr = ElementTree.Element(u"system-err") output = _text(scenario.captured.stderr) text = u"\nCaptured stderr:\n%s\n" % output stderr.append(CDATA(text)) case.append(stderr) if scenario.status != Status.skipped or self.show_skipped: report.testcases.append(case)
def _process_scenario(self, scenario, report): """Process a scenario and append information to JUnit report object. This corresponds to a JUnit testcase: * testcase.@classname = f(filename) +'.'+ feature.name * testcase.@name = scenario.name * testcase.@status = scenario.status * testcase.@time = scenario.duration Distinguishes now between failures and errors. Failures are AssertationErrors: expectation is violated/not met. Errors are unexpected RuntimeErrors (all other exceptions). If a failure/error occurs, the step, that caused the failure, and its location are provided now. :param scenario: Scenario to process. :param report: Context object to store/add info to (outgoing param). """ # pylint: disable=too-many-locals, too-many-branches, too-many-statements assert isinstance(scenario, Scenario) assert not isinstance(scenario, ScenarioOutline) if scenario.status != Status.skipped or self.show_skipped: # -- NOTE: Count only if not-skipped or skipped should be shown. report.counts_tests += 1 classname = report.classname feature = report.feature feature_name = feature.name if not feature_name: feature_name = self.make_feature_filename(feature) case = ElementTree.Element('testcase') case.set("classname", "%s.%s" % (classname, feature_name)) case.set("name", scenario.name or "") case.set("status", scenario.status.name) case.set("time", _text(round(scenario.duration, 6))) step = None failing_step = None if scenario.status == Status.failed: for status in (Status.failed, Status.undefined): step = self.select_step_with_status(status, scenario) if step: break # -- NOTE: Scenario may fail now due to hook-errors. element_name = "failure" if step and isinstance(step.exception, (AssertionError, type(None))): # -- FAILURE: AssertionError assert step.status in (Status.failed, Status.undefined) report.counts_failed += 1 else: # -- UNEXPECTED RUNTIME-ERROR: report.counts_errors += 1 element_name = "error" # -- COMMON-PART: failure = ElementTree.Element(element_name) if step: step_text = self.describe_step(step).rstrip() text = "\nFailing step: %s\nLocation: %s\n" % \ (step_text, step.location) message = _text(step.exception) failure.set('type', step.exception.__class__.__name__) failure.set('message', message) text += _text(step.error_message) else: # -- MAYBE: Hook failure before any step is executed. failure_type = "UnknownError" if scenario.exception: failure_type = scenario.exception.__class__.__name__ failure.set('type', failure_type) failure.set('message', scenario.error_message or "") traceback_lines = traceback.format_tb(scenario.exc_traceback) traceback_lines.insert(0, "Traceback:\n") text = _text("".join(traceback_lines)) failure.append(CDATA(text)) case.append(failure) elif (scenario.status in (Status.skipped, Status.untested) and self.show_skipped): report.counts_skipped += 1 step = self.select_step_with_status(Status.undefined, scenario) if step: # -- UNDEFINED-STEP: report.counts_failed += 1 failure = ElementTree.Element("failure") failure.set("type", "undefined") failure.set("message", ("Undefined Step: %s" % step.name)) case.append(failure) else: skip = ElementTree.Element('skipped') case.append(skip) # Create stdout section for each test case stdout = ElementTree.Element("system-out") text = "" if self.show_scenarios: text = self.describe_scenario(scenario) # Append the captured standard output if scenario.captured.stdout: output = _text(scenario.captured.stdout) text += "\nCaptured stdout:\n%s\n" % output stdout.append(CDATA(text)) case.append(stdout) # Create stderr section for each test case if scenario.captured.stderr: stderr = ElementTree.Element("system-err") output = _text(scenario.captured.stderr) text = "\nCaptured stderr:\n%s\n" % output stderr.append(CDATA(text)) case.append(stderr) if scenario.status != Status.skipped or self.show_skipped: report.testcases.append(case)
def store_exception_context(self, exception): self.exception = exception self.exc_traceback = traceback.format_tb(sys.exc_info()[2])