Skip to content

Commit

Permalink
fix UnicodeDecodeError when exception contains non-ascii characters (a…
Browse files Browse the repository at this point in the history
  • Loading branch information
sseliverstov authored and GilBecker-Anaplan committed Apr 10, 2023
1 parent fff0c5e commit 7836192
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 25 deletions.
30 changes: 30 additions & 0 deletions allure-behave/features/step.feature
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
Feature: Step

Scenario: Failed step
Given feature definition
"""
Feature: Step status
Scenario: Scenario with failed step
Given simple failed step
"""
When I run behave with allure formatter
Then allure report has a scenario with name "Scenario with failed step"
And scenario contains step "Given simple failed step"
And this step has "failed" status
And this step has status details with message "AssertionError: Assert message"


Scenario: Broken step
Given feature definition
"""
Feature: Step status
Scenario: Scenario with broken step
Given simple broken step
"""
When I run behave with allure formatter
Then allure report has a scenario with name "Scenario with broken step"
And scenario contains step "Given simple broken step"
And this step has "broken" status
And this step has status details with message "ZeroDivisionError"


Scenario: Step text parameter
Given feature definition
"""
Expand Down
1 change: 0 additions & 1 deletion allure-behave/features/steps/behave_steps.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import os
import sys
from tempfile import mkdtemp
from allure_commons_test.report import AllureReport
from behave.parser import Parser
Expand Down
15 changes: 15 additions & 0 deletions allure-behave/features/steps/dummy_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@ def step_impl(*args, **kwargs):
assert False, 'Assert message'


@given(u'провальный шаг')
def step_impl(*args, **kwargs):
assert False, u'Фиаско!'


@given(u'провальный шаг с ascii')
def step_impl(*args, **kwargs):
assert False, 'Фиаско!'


@given(u'проходящий шаг')
def step_impl(*args, **kwargs):
pass


@given(u'broken step')
@given(u'{what} broken step')
@given(u'broken step {where}')
Expand Down
10 changes: 10 additions & 0 deletions allure-behave/features/steps/report_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from allure_commons_test.result import has_step
from allure_commons_test.result import has_attachment
from allure_commons_test.result import has_parameter
from allure_commons_test.result import has_status_details
from allure_commons_test.result import with_status_message
from allure_commons_test.container import has_container
from allure_commons_test.container import has_before, has_after
from allure_commons_test.label import has_severity
Expand Down Expand Up @@ -80,6 +82,14 @@ def step_status(context, item, status):
assert_that(context.allure_report, matcher())


@then(u'{item} has status details with message "{message}"')
@then(u'this {item} has status details with message "{message}"')
def step_status(context, item, message):
context_matcher = getattr(context, item)
matcher = partial(context_matcher, has_status_details, with_status_message, message)
assert_that(context.allure_report, matcher())


@then(u'scenario has "{severity}" severity')
@then(u'this scenario has "{severity}" severity')
def step_severity(context, severity):
Expand Down
32 changes: 30 additions & 2 deletions allure-behave/features/unicode.feature
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Feature: Language
Scenario: Use russian
Given feature definition ru
Given feature definition ru
"""
Свойство: Юникод
Expand All @@ -18,4 +18,32 @@ Feature: Language
And scenario contains step "Пусть всегда будет солнце"
And scenario contains step "Пусть всегда будет небо"
And scenario contains step "Пусть всегда будет мама"
And scenario contains step "Пусть всегда буду я"
And scenario contains step "Пусть всегда буду я"


Scenario: Assert message in step
Given feature definition ru
"""
Свойство: Юникод
Сценарий: Ошибка с utf-8 сообщением
Допустим провальный шаг
"""
When I run behave with allure formatter with options "--lang ru"
Then allure report has a scenario with name "Ошибка с utf-8 сообщением"
And scenario contains step "Допустим провальный шаг"
And step has status details with message "AssertionError: Фиаско!"


Scenario: ASCII assert message in step
Given feature definition ru
"""
Свойство: Юникод
Сценарий: Ошибка с utf-8 сообщением
Допустим провальный шаг с ascii
"""
When I run behave with allure formatter with options "--lang ru"
Then allure report has a scenario with name "Ошибка с utf-8 сообщением"
And scenario contains step "Допустим провальный шаг с ascii"
And step has status details with message "AssertionError: Фиаско!"
22 changes: 10 additions & 12 deletions allure-behave/src/utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from behave.model import ScenarioOutline
from behave.runner_util import make_undefined_step_snippet
from allure_commons.types import Severity
from allure_commons.model2 import Status, Parameter, Label
from allure_commons.model2 import StatusDetails
from allure_commons.utils import md5
from allure_commons.utils import represent
import traceback
from allure_commons.utils import format_exception, format_traceback

STATUS = {
'passed': Status.PASSED,
Expand All @@ -28,7 +30,7 @@ def scenario_history_id(scenario):
parts = [scenario.feature.name, scenario.name]
if scenario._row:
row = scenario._row
parts.extend([u'{name}={value}'.format(name=name, value=value) for name, value in zip(row.headings, row.cells)])
parts.extend(['{name}={value}'.format(name=name, value=value) for name, value in zip(row.headings, row.cells)])
return md5(*parts)


Expand Down Expand Up @@ -71,10 +73,8 @@ def fixture_status(exception, exc_traceback):

def fixture_status_details(exception, exc_traceback):
if exception:
message = u','.join(map(str, exception.args))
message = u'{name}: {message}'.format(name=exception.__class__.__name__, message=message)
trace = u'\n'.join(traceback.format_tb(exc_traceback)) if exc_traceback else None
return StatusDetails(message=message, trace=trace)
return StatusDetails(message=format_exception(type(exception), exception),
trace=format_traceback(exc_traceback))
return None


Expand All @@ -87,12 +87,10 @@ def step_status(result):

def step_status_details(result):
if result.exception:
message = u','.join(map(lambda s: u'%s' % s, result.exception.args))
message = u'{name}: {message}'.format(name=result.exception.__class__.__name__, message=message)
trace = u'\n'.join(traceback.format_tb(result.exc_traceback)) if result.exc_traceback else None
return StatusDetails(message=message, trace=trace)
return StatusDetails(message=format_exception(type(result.exception), result.exception),
trace=format_traceback(result.exc_traceback))
elif result.status == 'undefined':
message = u'\nYou can implement step definitions for undefined steps with these snippets:\n\n'
message = '\nYou can implement step definitions for undefined steps with these snippets:\n\n'
message += make_undefined_step_snippet(result)
return StatusDetails(message=message)

Expand Down
3 changes: 2 additions & 1 deletion allure-python-commons-test/src/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
from hamcrest import all_of, anything
from hamcrest import equal_to, not_none
from hamcrest import has_entry, has_item
from hamcrest import contains_string


def has_step(name, *matchers):
Expand Down Expand Up @@ -129,7 +130,7 @@ def has_status_details(*matchers):


def with_status_message(message):
return has_entry('message', message)
return has_entry('message', contains_string(message))


def has_history_id():
Expand Down
2 changes: 1 addition & 1 deletion allure-python-commons/src/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def _some_str(value):
return str(value)
except UnicodeError:
try:
value = unicode(value) # noqa: F821
value = unicode(value)
return value.encode('utf-8', 'replace')
except Exception:
pass
Expand Down
80 changes: 72 additions & 8 deletions allure-python-commons/src/utils.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
# -*- coding: utf-8 -*-

import sys
import six
import time
import uuid
import inspect
import hashlib
import platform
import traceback

from six import text_type
if sys.version_info.major > 2:
from traceback import format_exception_only
else:
from _compat import format_exception_only


def md5(*args):
Expand Down Expand Up @@ -40,16 +46,19 @@ def represent(item):
>>> represent(123)
'123'
>>> represent('hi') == u"'hi'"
True
>>> represent(u'привет') == u"'привет'"
>>> from sys import version_info
>>> expected = u"'hi'" if version_info.major < 3 else "'hi'"
>>> represent('hi') == expected
True
>>> from sys import version_info
>>> represent(str(bytearray([0xd0, 0xbf]))) == u"'\u043f'" if version_info.major < 3 else True
>>> expected = u"'привет'" if version_info.major < 3 else "'привет'"
>>> represent(u'привет') == expected
True
>>> represent(bytearray([0xd0, 0xbf])) # doctest: +ELLIPSIS
"<... 'bytearray'>"
>>> from struct import pack
>>> result = "<type 'str'>" if version_info.major < 3 else "<class 'bytes'>"
>>> represent(pack('h', 0x89)) == result
Expand Down Expand Up @@ -78,7 +87,7 @@ def represent(item):
except UnicodeDecodeError:
pass

if isinstance(item, text_type):
if isinstance(item, six.text_type):
return u'\'%s\'' % item
elif isinstance(item, (bytes, bytearray)):
return repr(type(item))
Expand All @@ -88,8 +97,63 @@ def represent(item):

def func_parameters(func, *a, **kw):
bowels = inspect.getargspec(func) if sys.version_info.major < 3 else inspect.getfullargspec(func)
args_dict = dict(zip(bowels.args, map(represent, a)))
args_dict = dict(zip(bowels.args, map(represent, a)))
kwargs_dict = dict(zip(kw, list(map(lambda i: represent(kw[i]), kw))))
kwarg_defaults = dict(zip(reversed(bowels.args), reversed(list(map(represent, bowels.defaults or ())))))
kwarg_defaults.update(kwargs_dict)
return args_dict, kwarg_defaults


def format_traceback(exc_traceback):
return ''.join(traceback.format_tb(exc_traceback)) if exc_traceback else None


def format_exception(etype, value):
"""
>>> import sys
>>> try:
... assert False, u'Привет'
... except AssertionError:
... etype, e, _ = sys.exc_info()
... format_exception(etype, e) # doctest: +ELLIPSIS
'AssertionError: ...\\n'
>>> try:
... assert False, 'Привет'
... except AssertionError:
... etype, e, _ = sys.exc_info()
... format_exception(etype, e) # doctest: +ELLIPSIS
'AssertionError: ...\\n'
>>> try:
... compile("bla u'Привет'", "fake.py", "exec")
... except SyntaxError:
... etype, e, _ = sys.exc_info()
... format_exception(etype, e) # doctest: +ELLIPSIS
' File "fake.py", line 1...SyntaxError: invalid syntax\\n'
>>> try:
... compile("bla 'Привет'", "fake.py", "exec")
... except SyntaxError:
... etype, e, _ = sys.exc_info()
... format_exception(etype, e) # doctest: +ELLIPSIS
' File "fake.py", line 1...SyntaxError: invalid syntax\\n'
>>> from hamcrest import assert_that, equal_to
>>> try:
... assert_that('left', equal_to('right'))
... except AssertionError:
... etype, e, _ = sys.exc_info()
... format_exception(etype, e) # doctest: +ELLIPSIS
"AssertionError: \\nExpected:...but:..."
>>> try:
... assert_that(u'left', equal_to(u'right'))
... except AssertionError:
... etype, e, _ = sys.exc_info()
... format_exception(etype, e) # doctest: +ELLIPSIS
"AssertionError: \\nExpected:...but:..."
"""
return '\n'.join(format_exception_only(etype, value))

0 comments on commit 7836192

Please sign in to comment.