Skip to content
This repository has been archived by the owner on Jan 14, 2019. It is now read-only.

Commit

Permalink
Merge pull request #55 from 1tush/py3
Browse files Browse the repository at this point in the history
Python3 compatible (3.3, 3.4)
  • Loading branch information
pupssman committed Feb 5, 2015
2 parents 9be4579 + 645e876 commit e1106f2
Show file tree
Hide file tree
Showing 19 changed files with 197 additions and 110 deletions.
6 changes: 4 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
language: python
python:
- '2.7'
- '2.7'
- '3.3'
- '3.4'
env:
- TOX_ENV=py27
- TOX_ENV=py
- TOX_ENV=static_check
install: pip install tox
script: tox -e $TOX_ENV
Expand Down
13 changes: 12 additions & 1 deletion allure/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@


# providing decorators via allure.xxx instead of pytest.allure.xxx
__methods_to_provide = ['step', 'attach', 'single_step', 'label', 'feature', 'story', 'severity', 'issue', 'environment']
__methods_to_provide = [
'step',
'attach',
'single_step',
'label',
'feature',
'story',
'severity',
'issue',
'environment',
'attach_type',
]

for method in __methods_to_provide:
globals()[method] = getattr(MASTER_HELPER, method)
9 changes: 6 additions & 3 deletions allure/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
@author: pupssman
"""
from six import text_type, iteritems
from contextlib import contextmanager
from functools import wraps
import os
Expand All @@ -20,6 +21,7 @@


class StepContext:

def __init__(self, allure, title):
self.allure = allure
self.title = title
Expand Down Expand Up @@ -53,6 +55,7 @@ def impl(*a, **kw):


class AllureImpl(object):

"""
Allure test-flow implementation that handles test data creation.
Devised to be used as base layer for allure adaptation to arbitrary test frameworks.
Expand Down Expand Up @@ -187,7 +190,7 @@ def store_environment(self):
return

environment = Environment(id=uuid.uuid4(), name="Allure environment parameters", parameters=[])
for key, value in self.environment.iteritems():
for key, value in iteritems(self.environment):
environment.parameters.append(EnvParameter(name=key, key=key, value=value))

with self._reportfile('environment.xml') as f:
Expand All @@ -200,7 +203,7 @@ def _save_attach(self, body, attach_type=AttachmentType.TEXT):
:arg body: str or unicode with contents. str is written as-is in byte stream, unicode is written as utf-8 (what do you expect else?)
"""
with self._attachfile("%s-attachment.%s" % (uuid.uuid4(), attach_type.extension)) as f:
if isinstance(body, unicode):
if isinstance(body, text_type):
f.write(body.encode('utf-8'))
else:
f.write(body)
Expand Down Expand Up @@ -232,4 +235,4 @@ def _reportfile(self, filename):
logfile.close()

def _write_xml(self, logfile, xmlfied):
logfile.write(etree.tostring(xmlfied.toxml(), pretty_print=True, xml_declaration=False, encoding=unicode))
logfile.write(etree.tostring(xmlfied.toxml(), pretty_print=True, xml_declaration=False, encoding=text_type))
7 changes: 4 additions & 3 deletions allure/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from enum import Enum


class Status:
class Status(object):
FAILED = 'failed'
BROKEN = 'broken'
PASSED = 'passed'
Expand All @@ -17,15 +17,15 @@ class Status:
PENDING = 'pending'


class Label:
class Label(object):
DEFAULT = 'allure_label'
FEATURE = 'feature'
STORY = 'story'
SEVERITY = 'severity'
ISSUE = 'issue'


class Severity:
class Severity(object):
BLOCKER = 'blocker'
CRITICAL = 'critical'
NORMAL = 'normal'
Expand All @@ -34,6 +34,7 @@ class Severity:


class AttachmentType(Enum):

def __init__(self, mime_type, extension):
self.mime_type = mime_type
self.extension = extension
Expand Down
14 changes: 10 additions & 4 deletions allure/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,12 @@ def pytest_runtest_setup(item):


class LazyInitStepContext(StepContext):

"""
This is a step context used for decorated steps.
It provides a possibility to create step decorators, being initiated before pytest_configure, when no AllureListener initiated yet.
"""

def __init__(self, allure_helper, title):
self.allure_helper = allure_helper
self.title = title
Expand All @@ -102,9 +104,11 @@ def allure(self):


class AllureHelper(object):

"""
This object holds various utility methods used from ``pytest.allure`` namespace, like ``pytest.allure.attach``
"""

def __init__(self):
self._allurelistener = None # FIXME: this gets injected elsewhere, like in the pytest_configure

Expand All @@ -122,9 +126,7 @@ def label(self, name, *value):
"""
A decorator factory that returns ``pytest.mark`` for a given label.
"""

allure_label = getattr(pytest.mark, '%s.%s' %
(Label.DEFAULT, name.encode('utf-8', 'ignore')))
allure_label = getattr(pytest.mark, '%s.%s' % (Label.DEFAULT, name))
return allure_label(*value)

def severity(self, severity):
Expand Down Expand Up @@ -235,9 +237,11 @@ def pytest_namespace():


class AllureTestListener(object):

"""
Listens to pytest hooks to generate reports for common tests.
"""

def __init__(self, logdir, config):
self.impl = AllureImpl(logdir)
self.config = config
Expand Down Expand Up @@ -301,7 +305,7 @@ def pytest_runtest_logreport(self, report):
else:
self._stop_case(report, status=Status.FAILED)
elif report.skipped:
self._stop_case(report, status=Status.SKIPPED)
self._stop_case(report, status=Status.SKIPPED)

def pytest_runtest_makereport(self, item, call, __multicall__): # @UnusedVariable
"""
Expand All @@ -325,10 +329,12 @@ def pytest_sessionfinish(self):


class AllureCollectionListener(object):

"""
Listens to pytest collection-related hooks
to generate reports for modules that failed to collect.
"""

def __init__(self, logdir):
self.impl = AllureImpl(logdir)
self.fails = []
Expand Down
32 changes: 20 additions & 12 deletions allure/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

__author__ = 'pupssman'

from six import u, unichr, text_type, binary_type

import re
import sys

from lxml import objectify
from recordtype import recordtype
from namedlist import namedlist


def element_maker(name, namespace):
Expand Down Expand Up @@ -36,58 +38,62 @@ def check(self, what):
# XML 1.0 which are valid in this python build. Hence we calculate
# this dynamically instead of hardcoding it. The spec range of valid
# chars is: Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
# | [#x10000-#x10FFFF]
# | [#x10000-#x10FFFF]
_legal_chars = (0x09, 0x0A, 0x0d)
_legal_ranges = (
(0x20, 0x7E),
(0x80, 0xD7FF),
(0xE000, 0xFFFD),
(0x10000, 0x10FFFF),
)
_legal_xml_re = [unicode("%s-%s") % (unichr(low), unichr(high)) for (low, high) in _legal_ranges if low < sys.maxunicode]
_legal_xml_re = [u("%s-%s") % (unichr(low), unichr(high)) for (low, high) in _legal_ranges if low < sys.maxunicode]
_legal_xml_re = [unichr(x) for x in _legal_chars] + _legal_xml_re
illegal_xml_re = re.compile(unicode('[^%s]') % unicode('').join(_legal_xml_re))
illegal_xml_re = re.compile(u('[^%s]') % u('').join(_legal_xml_re))


def legalize_xml(arg):
def repl(matchobj):
i = ord(matchobj.group())
if i <= 0xFF:
return unicode('#x%02X') % i
return u('#x%02X') % i
else:
return unicode('#x%04X') % i
return u('#x%04X') % i
return illegal_xml_re.sub(repl, arg)


class Element(Rule):

def __init__(self, name='', namespace=''):
self.name = name
self.namespace = namespace

def value(self, name, what):
if not isinstance(what, basestring):
if not isinstance(what, (text_type, binary_type)):
return self.value(name, str(what))

if not isinstance(what, unicode):
if not isinstance(what, text_type):
try:
what = unicode(what, 'utf-8')
what = text_type(what, 'utf-8')
except UnicodeDecodeError:
what = unicode(what, 'utf-8', errors='replace')
what = text_type(what, 'utf-8', errors='replace')

return element_maker(self.name or name, self.namespace)(legalize_xml(what))


class Attribute(Rule):

def value(self, name, what):
return str(what)


class Nested(Rule):

def value(self, name, what):
return what.toxml()


class Many(Rule):

def __init__(self, rule, name='', namespace=''):
self.rule = rule
self.name = name
Expand All @@ -98,15 +104,17 @@ def value(self, name, what):


class WrappedMany(Many):

def value(self, name, what):
values = super(WrappedMany, self).value(name, what)
return element_maker(self.name or name, self.namespace)(*values)


def xmlfied(el_name, namespace='', fields=[], **kw):
items = fields + kw.items()
items = fields + list(kw.items())

class MyImpl(namedlist('XMLFied', [(item[0], None) for item in items])):

class MyImpl(recordtype('XMLFied', [(item[0], None) for item in items])):
def toxml(self):
el = element_maker(el_name, namespace)
entries = lambda clazz: [(name, rule.value(name, getattr(self, name))) for (name, rule) in items if isinstance(rule, clazz) and rule.check(getattr(self, name))]
Expand Down
42 changes: 25 additions & 17 deletions allure/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
@author: pupssman
'''

from six import text_type, binary_type, python_2_unicode_compatible, u
from six.moves import filter
import time
import hashlib
import inspect
Expand All @@ -34,7 +36,7 @@ def parents_of(item):


def parent_module(item):
return filter(lambda x: isinstance(x, Module), parents_of(item))[0]
return next(filter(lambda x: isinstance(x, Module), parents_of(item)))


def parent_down_from_module(item):
Expand Down Expand Up @@ -72,8 +74,8 @@ def get_marker_that_starts_with(item, name):
suitable_names = filter(lambda x: x.startswith(name), item.keywords.keys())

markers = list()
for name in suitable_names:
markers.append(item.get_marker(name))
for suitable_name in suitable_names:
markers.append(item.get_marker(suitable_name))

return markers

Expand All @@ -91,23 +93,25 @@ def all_of(enum):
"""
returns list of name-value pairs for ``enum`` from :py:mod:`allure.constants`
"""
return filter(lambda (n, v): not n.startswith('_'), inspect.getmembers(enum))
def clear_pairs(pair):
if pair[0].startswith('_'):
return False
if pair[0] in ('name', 'value'):
return False
return True
return filter(clear_pairs, inspect.getmembers(enum))


def unicodify(something):
def convert(x):
try:
return unicode(x)
except UnicodeDecodeError:
return unicode(x, 'utf-8', errors='replace')

try:
return convert(something) # @UndefinedVariable
except TypeError:
if isinstance(something, text_type):
return something
elif isinstance(something, binary_type):
return something.decode('utf-8', errors='replace')
else:
try:
return convert(str(something)) # @UndefinedVariable
except UnicodeEncodeError:
return convert('<nonpresentable %s>' % type(something)) # @UndefinedVariable
return text_type(something) # @UndefinedVariable
except (UnicodeEncodeError, UnicodeDecodeError):
return u'<nonpresentable %s>' % type(something) # @UndefinedVariable


def present_exception(e):
Expand All @@ -127,6 +131,7 @@ def get_exception_message(report):
return (getattr(report, 'exception', None) and present_exception(report.exception.value)) or (hasattr(report, 'result') and report.result) or report.outcome


@python_2_unicode_compatible
class LabelsList(list):

def __eq__(self, other):
Expand Down Expand Up @@ -154,4 +159,7 @@ def __and__(self, other):
return result

def __str__(self):
return ', '.join(map(str, [(el.name, el.value) for el in self]))
return u(', ').join(map(text_type, [(el.name, el.value) for el in self]))

def __bytes__(self):
return self.__str__().encode('utf-8')
Loading

0 comments on commit e1106f2

Please sign in to comment.