diff --git a/allure/__init__.py b/allure/__init__.py index 14a1564..ef6e621 100644 --- a/allure/__init__.py +++ b/allure/__init__.py @@ -2,7 +2,7 @@ # providing decorators via allure.xxx instead of pytest.allure.xxx -__methods_to_provide = ['step', 'attach', 'single_step', 'label', 'feature', 'story'] +__methods_to_provide = ['step', 'attach', 'single_step', 'label', 'feature', 'story', 'severity'] for method in __methods_to_provide: globals()[method] = getattr(MASTER_HELPER, method) diff --git a/allure/common.py b/allure/common.py index f16d7eb..842069b 100644 --- a/allure/common.py +++ b/allure/common.py @@ -12,7 +12,7 @@ from lxml import etree import py -from allure.constants import AttachmentType, Severity, Status +from allure.constants import AttachmentType, Status from allure.structure import Attach, TestStep, TestCase, TestSuite, Failure from allure.utils import now @@ -27,7 +27,7 @@ def __enter__(self): if self.allure: self.step = self.allure.start_step(self.title) - def __exit__(self, exc_type, exc_val, exc_tb): # @UnusedVariable + def __exit__(self, exc_type, exc_val, exc_tb): if self.allure: if exc_type is not None: if exc_type == Skipped: @@ -102,7 +102,7 @@ def attach(self, title, contents, attach_type): """ attach = Attach(source=self._save_attach(contents, attach_type=attach_type), title=title, - type=attach_type) + type=attach_type.mime_type) self.stack[-1].attachments.append(attach) def start_step(self, name): @@ -126,14 +126,12 @@ def stop_step(self): step = self.stack.pop() step.stop = now() - def start_case(self, name, description=None, severity=Severity.NORMAL, - labels=None): + def start_case(self, name, description=None, labels=None): """ Starts a new :py:class:`allure.structure.TestCase` """ test = TestCase(name=name, description=description, - severity=severity, start=now(), attachments=[], labels=labels or [], @@ -187,9 +185,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?) """ - - # FIXME: we should generate attachment name properly - with self._attachfile("%s-attachment.%s" % (uuid.uuid4(), attach_type)) as f: + with self._attachfile("%s-attachment.%s" % (uuid.uuid4(), attach_type.extension)) as f: if isinstance(body, unicode): f.write(body.encode('utf-8')) else: @@ -214,7 +210,7 @@ def _reportfile(self, filename): reportpath = os.path.join(self.logdir, filename) encoding = 'utf-8' - logfile = py.std.codecs.open(reportpath, 'w', encoding=encoding) # @UndefinedVariable + logfile = py.std.codecs.open(reportpath, 'w', encoding=encoding) try: yield logfile diff --git a/allure/constants.py b/allure/constants.py index faf2979..5147b6c 100644 --- a/allure/constants.py +++ b/allure/constants.py @@ -5,40 +5,46 @@ @author: pupssman ''' - - -class Severity: - BLOCKER = 'blocker' - CRITICAL = 'critical' - NORMAL = 'normal' - MINOR = 'minor' - TRIVIAL = 'trivial' +from enum import Enum class Status: FAILED = 'failed' BROKEN = 'broken' PASSED = 'passed' - SKIPPED = 'skipped' + SKIPPED = 'canceled' + CANCELED = 'canceled' + PENDING = 'pending' -FAILED_STATUSES = [Status.FAILED, Status.BROKEN] +class Label: + DEFAULT = 'allure_label' + FEATURE = 'feature' + STORY = 'story' + SEVERITY = 'severity' -class AttachmentType: - TEXT = "txt" - HTML = "html" - XML = "xml" - PNG = "png" - JPG = "jpg" - JSON = "json" - OTHER = "other" +class Severity: + BLOCKER = 'blocker' + CRITICAL = 'critical' + NORMAL = 'normal' + MINOR = 'minor' + TRIVIAL = 'trivial' -ALLURE_NAMESPACE = "urn:model.allure.qatools.yandex.ru" +class AttachmentType(Enum): + def __init__(self, mime_type, extension): + self.mime_type = mime_type + self.extension = extension + TEXT = ("text/plain", "txt") + HTML = ("application/html", "html") + XML = ("application/xml", "xml") + PNG = ("image/png", "png") + JPG = ("image/jpg", "jpg") + JSON = ("application/json", "json") + OTHER = ("other", "other") -class Label: - DEFAULT = 'allure_label' - FEATURE = 'feature' - STORY = 'story' + +ALLURE_NAMESPACE = "urn:model.allure.qatools.yandex.ru" +FAILED_STATUSES = [Status.FAILED, Status.BROKEN] diff --git a/allure/pytest_plugin.py b/allure/pytest_plugin.py index ff62e04..185ec7a 100644 --- a/allure/pytest_plugin.py +++ b/allure/pytest_plugin.py @@ -8,8 +8,8 @@ from allure.utils import LabelsList from allure.constants import Status, AttachmentType, Severity, \ FAILED_STATUSES, Label -from allure.utils import parent_module, parent_down_from_module, severity_of, \ - labels_of, all_of, get_exception_message +from allure.utils import parent_module, parent_down_from_module, labels_of, \ + all_of, get_exception_message from allure.structure import TestLabel @@ -23,12 +23,12 @@ def pytest_addoption(parser): severities = [v for (_, v) in all_of(Severity)] - def severity_type(string): - entries = [x.strip() for x in string.split(',')] + def severity_label_type(string): + entries = LabelsList([TestLabel(name=Label.SEVERITY, value=x) for x in string.split(',')]) for entry in entries: - if entry not in severities: - raise argparse.ArgumentTypeError('Illegal severity value [%s], only values from [%s] are allowed.' % (entry, ', '.join(severities))) + if entry.value not in severities: + raise argparse.ArgumentTypeError('Illegal severity value [%s], only values from [%s] are allowed.' % (entry.value, ', '.join(severities))) return entries @@ -42,8 +42,8 @@ def stories_label_type(string): action="store", dest="allureseverities", metavar="SEVERITIES_LIST", - default=None, - type=severity_type, + default=LabelsList(), + type=severity_label_type, help="""Comma-separated list of severity names. Tests only with these severities will be run. Possible values are:%s.""" % ', '.join(severities)) @@ -77,15 +77,10 @@ def pytest_configure(config): def pytest_runtest_setup(item): - severity = severity_of(item) item_labels = labels_of(item) option = item.config.option - if option.allureseverities and severity not in \ - option.allureseverities: - pytest.skip("Not running test of severity %s." % severity) - - arg_labels = option.allurefeatures + option.allurestories + arg_labels = option.allurefeatures + option.allurestories + option.allureseverities if arg_labels and not item_labels & arg_labels: pytest.skip('Not suitable with selected labels: %s.' % arg_labels) @@ -123,12 +118,6 @@ def attach(self, name, contents, type=AttachmentType.TEXT): # @ReservedAssignme if self._allurelistener: self._allurelistener.attach(name, contents, type) - def severity(self, level): - """ - A decorator factory that returns ``pytest.mark`` for a given allure ``level``. - """ - return pytest.mark.allure_severity(level) - def label(self, name, *value): """ A decorator factory that returns ``pytest.mark`` for a given label. @@ -138,6 +127,12 @@ def label(self, name, *value): (Label.DEFAULT, name.encode('utf-8', 'ignore'))) return allure_label(*value) + def severity(self, severity): + """ + A decorator factory that returns ``pytest.mark`` for a given allure ``level``. + """ + return self.label(Label.SEVERITY, severity) + def feature(self, *features): """ A decorator factory that returns ``pytest.mark`` for a given features. @@ -277,8 +272,7 @@ def pytest_runtest_protocol(self, __multicall__, item, nextitem): self.testsuite = 'Yes' name = '.'.join(mangle_testnames([x.name for x in parent_down_from_module(item)])) - self.impl.start_case(name, description=item.function.__doc__, severity=severity_of(item), - labels=labels_of(item)) + self.impl.start_case(name, description=item.function.__doc__, labels=labels_of(item)) result = __multicall__.execute() if not nextitem or parent_module(item) != parent_module(nextitem): diff --git a/allure/structure.py b/allure/structure.py index d6d58f4..02290e7 100644 --- a/allure/structure.py +++ b/allure/structure.py @@ -31,8 +31,7 @@ labels=Many(Nested()), status=Attribute(), start=Attribute(), - stop=Attribute(), - severity=Attribute()) + stop=Attribute()) TestSuite = xmlfied('test-suite', diff --git a/allure/utils.py b/allure/utils.py index 1c3a3da..1e4478b 100644 --- a/allure/utils.py +++ b/allure/utils.py @@ -15,7 +15,7 @@ from _pytest.python import Module -from allure.constants import Severity, Label +from allure.constants import Label from allure.structure import TestLabel @@ -60,14 +60,6 @@ def now(): return sec2ms(time.time()) -def severity_of(item): - severity_marker = item.get_marker('allure_severity') - if severity_marker: - return severity_marker.args[0] - else: - return Severity.NORMAL - - def labels_of(item): """ Returns list of TestLabel elements. diff --git a/setup.py b/setup.py index 7ea4339..9343f8c 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- PACKAGE = "pytest-allure-adaptor" -VERSION = "1.4.1" +VERSION = "1.5.0" import os from setuptools import setup @@ -24,5 +24,6 @@ def read(fname): install_requires=[ "lxml>=3.2.0", "pytest>=2.4.1", - "recordtype"] + "recordtype", + "enum34"] ) diff --git a/tests/allure.xsd b/tests/allure.xsd index cf3d55b..75f31aa 100644 --- a/tests/allure.xsd +++ b/tests/allure.xsd @@ -1,165 +1,173 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index 34a8e45..c8a920f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,15 +3,13 @@ from lxml import etree, objectify -from hamcrest.core.helpers.wrap_matcher import wrap_matcher -from hamcrest.core.base_matcher import BaseMatcher - pytest_plugins = ["pytester"] @pytest.fixture def schema(): """ + schema copied from https://github.com/allure-framework/allure-core/blob/allure-core-1.4.1/allure-model/src/main/resources/allure.xsd Returns :py:class:`lxml.etree.XMLSchema` object configured with schema for reports """ path_to_schema = os.path.join(os.path.dirname(__file__), 'allure.xsd') @@ -57,20 +55,3 @@ def impl(*a, **kw): return reports[0] return impl - - -class HasFloat(BaseMatcher): - - def __init__(self, str_matcher): - self.str_matcher = str_matcher - - def _matches(self, item): - return self.str_matcher.matches(float(item)) - - def describe_to(self, description): - description.append_text('an object with float ') \ - .append_description_of(self.str_matcher) - - -def has_float(match): - return HasFloat(wrap_matcher(match)) diff --git a/tests/matchers.py b/tests/matchers.py new file mode 100644 index 0000000..7b3b483 --- /dev/null +++ b/tests/matchers.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +__author__ = 'mavlyutov@yandex-team.ru' + +from hamcrest.core.helpers.wrap_matcher import wrap_matcher +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest import equal_to, has_property, has_properties, has_item, anything + + +class HasFloat(BaseMatcher): + + def __init__(self, str_matcher): + self.str_matcher = str_matcher + + def _matches(self, item): + return self.str_matcher.matches(float(item)) + + def describe_to(self, description): + description.append_text('an object with float ') \ + .append_description_of(self.str_matcher) + + +def has_float(match): + return HasFloat(wrap_matcher(match)) + + +def has_label(test_name, label_name=anything(), label_value=anything()): + return has_property('{}test-cases', + has_property('test-case', + has_item( + has_properties({'name': equal_to(test_name), + 'labels': has_property('label', + has_item( + has_property('attrib', equal_to( + {'name': label_name, + 'value': label_value}))))})))) diff --git a/tests/test_attach.py b/tests/test_attach.py index 26281d0..d5083e2 100644 --- a/tests/test_attach.py +++ b/tests/test_attach.py @@ -36,7 +36,7 @@ def test_x(): A.attach('Foo', 'Bar', A.attach_type.%s) """ % a_type) - assert_that(report.find('.//attachment').attrib, has_entries(title='Foo', type=getattr(AttachmentType, a_type))) + assert_that(report.find('.//attachment').attrib, has_entries(title='Foo', type=getattr(AttachmentType, a_type).mime_type)) class TestContents: diff --git a/tests/test_labels.py b/tests/test_labels.py index ae72f2b..a2bceb3 100644 --- a/tests/test_labels.py +++ b/tests/test_labels.py @@ -7,21 +7,10 @@ """ import pytest +from matchers import has_label from hamcrest import assert_that, equal_to, has_length, is_not, has_property, has_properties, has_item, anything, all_of, any_of -def has_label(test_name, label_name=anything(), label_value=anything()): - return has_property('{}test-cases', - has_property('test-case', - has_item( - has_properties({'name': equal_to(test_name), - 'labels': has_property('label', - has_item( - has_property('attrib', equal_to( - {'name': label_name, - 'value': label_value}))))})))) - - def has_label_length(test_name, label_length): return has_property('{}test-cases', has_property('test-case', diff --git a/tests/test_severity.py b/tests/test_severity.py index bcf613a..d7d949e 100644 --- a/tests/test_severity.py +++ b/tests/test_severity.py @@ -6,12 +6,23 @@ @author: pupssman """ -from hamcrest import assert_that, contains, all_of, has_entry, has_property +from hamcrest import assert_that, contains, all_of, has_entry, has_property, has_properties, contains_inanyorder, has_item, equal_to from allure.constants import Severity, Status from allure import utils +from matchers import has_label import pytest +def severity_element(value): + return has_properties(attrib=all_of( + has_entry('name', 'severity'), + has_entry('value', value))) + + +def has_test_with_severity(test_name, severity_level): + return has_label(test_name, label_value=severity_level, label_name='severity') + + @pytest.mark.parametrize('mark_way', [ '@pytest.allure.%s', '@pytest.allure.severity(pytest.allure.severity_level.%s)' @@ -26,7 +37,7 @@ def test_foo(): pass """ % (mark_way % name)) - assert_that(report.xpath(".//test-case/@severity"), contains(value)) + assert_that(report.findall(".//test-case/labels/label"), contains(severity_element(value))) def test_class_severity(report_for): @@ -47,7 +58,11 @@ def test_b(self): pass """) - assert_that(report.xpath(".//test-case/@severity"), contains(Severity.CRITICAL, Severity.TRIVIAL)) + assert_that(report, all_of( + has_test_with_severity('TestMy.test_a', Severity.CRITICAL), + has_test_with_severity('TestMy.test_b', Severity.TRIVIAL), + has_test_with_severity('TestMy.test_b', Severity.CRITICAL) + )) def test_module_severity(report_for): @@ -77,7 +92,16 @@ def test_b(self): pass """) - assert_that(report.xpath(".//test-case/@severity"), contains(Severity.MINOR, Severity.NORMAL, Severity.CRITICAL, Severity.TRIVIAL)) + assert_that(report, all_of( + has_test_with_severity('test_m', Severity.MINOR), + has_test_with_severity('test_n', Severity.MINOR), + has_test_with_severity('test_n', Severity.NORMAL), + has_test_with_severity('TestMy.test_a', Severity.MINOR), + has_test_with_severity('TestMy.test_a', Severity.CRITICAL), + has_test_with_severity('TestMy.test_b', Severity.MINOR), + has_test_with_severity('TestMy.test_b', Severity.CRITICAL), + has_test_with_severity('TestMy.test_b', Severity.TRIVIAL) + )) @pytest.mark.parametrize('severities', [ @@ -98,19 +122,18 @@ def test_run_only(report_for, severities): def test_a(): pass + @pytest.allure.MINOR def test_b(): pass - @pytest.allure.MINOR def test_c(): pass """, extra_run_args=['--allure_severities', ','.join(severities)]) - a_status, b_status, c_status = [Status.PASSED if s in severities else Status.SKIPPED for s in [Severity.CRITICAL, Severity.NORMAL, Severity.MINOR]] + a_status, b_status, c_status = [Status.PASSED if s in severities else Status.SKIPPED for s in [Severity.CRITICAL, Severity.MINOR, '']] - assert_that(report.xpath(".//test-case"), contains(all_of(has_property('name', 'test_a'), - has_property('attrib', has_entry('status', a_status))), - all_of(has_property('name', 'test_b'), - has_property('attrib', has_entry('status', b_status))), - all_of(has_property('name', 'test_c'), - has_property('attrib', has_entry('status', c_status))))) + assert_that(report.xpath(".//test-case"), contains( + all_of(has_property('name', 'test_a'), has_property('attrib', has_entry('status', a_status))), + all_of(has_property('name', 'test_b'), has_property('attrib', has_entry('status', b_status))), + all_of(has_property('name', 'test_c'), has_property('attrib', has_entry('status', c_status))) + )) diff --git a/tests/test_smoke.py b/tests/test_smoke.py index b2c366e..27409ac 100644 --- a/tests/test_smoke.py +++ b/tests/test_smoke.py @@ -10,10 +10,10 @@ import time import pytest -from hamcrest import is_, assert_that, contains, has_property, all_of, has_entry, greater_than, less_than, has_entries, contains_inanyorder, is_not, has_items, starts_with +from hamcrest import is_, assert_that, contains, has_property, all_of, has_entry, greater_than, less_than, \ + has_entries, contains_inanyorder, is_not, has_items, starts_with from allure.constants import Status - -from conftest import has_float +from matchers import has_float @pytest.mark.parametrize('statement', ['assert 1', 'assert 0', diff --git a/tests/test_steps.py b/tests/test_steps.py index c5b6133..25fada9 100644 --- a/tests/test_steps.py +++ b/tests/test_steps.py @@ -12,7 +12,7 @@ from hamcrest.library.number.ordering_comparison import greater_than_or_equal_to, \ less_than_or_equal_to from hamcrest.core.core.allof import all_of -from tests.conftest import has_float +from matchers import has_float from allure.constants import Status import pytest