Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jobs:
package: [
allure-python-commons-test,
allure-python-commons,
allure-nose2,
allure-behave,
allure-pytest,
allure-pytest-bdd,
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ jobs:
twine upload dist/*
popd

pushd allure-nose2
python setup.py sdist bdist_wheel
twine upload dist/*
popd

pushd allure-pytest
python setup.py sdist bdist_wheel
twine upload dist/*
Expand Down
Empty file added allure-nose2/README.rst
Empty file.
58 changes: 58 additions & 0 deletions allure-nose2/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import os
from setuptools import setup

PACKAGE = "allure-nose2"

classifiers = [
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Topic :: Software Development :: Quality Assurance',
'Topic :: Software Development :: Testing',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
]

setup_requires = [
"setuptools_scm"
]

install_requires = [
"nose2"
]


def prepare_version():
from setuptools_scm import get_version
configuration = {"root": "..", "relative_to": __file__}
version = get_version(**configuration)
install_requires.append("allure-python-commons=={version}".format(version=version))
return configuration


def get_readme(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()


def main():
setup(
name=PACKAGE,
use_scm_version=prepare_version,
description="Allure nose2 integration",
url="https://github.com/allure-framework/allure-python",
author="QAMetaSoftware, Stanislav Seliverstov",
author_email="sseliverstov@qameta.io",
license="Apache-2.0",
classifiers=classifiers,
keywords="allure reporting nose2",
long_description=get_readme('README.rst'),
packages=["allure_nose2"],
package_dir={"allure_nose2": "src"},
setup_requires=setup_requires,
install_requires=install_requires
)

if __name__ == '__main__':
main()

Empty file added allure-nose2/src/__init__.py
Empty file.
5 changes: 5 additions & 0 deletions allure-nose2/src/listener.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

# ToDo attaches
class AllureListener(object):
def __init__(self, lifecycle):
self.lifecycle = lifecycle
129 changes: 129 additions & 0 deletions allure-nose2/src/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
from nose2.events import Plugin
from allure_commons import plugin_manager
from allure_commons.logger import AllureFileLogger
from allure_nose2.listener import AllureListener
from allure_commons.lifecycle import AllureLifecycle
from nose2 import result
from allure_commons.model2 import Status
from allure_commons.model2 import StatusDetails
from allure_commons.model2 import Label
from allure_commons.types import LabelType
from allure_commons.utils import host_tag, thread_tag

from allure_commons.utils import platform_label, md5


from .utils import timestamp_millis, status_details, update_attrs, labels, name, fullname, params
import allure_commons


class DecoratorsHelper(object):
@classmethod
@allure_commons.hookimpl
def decorate_as_label(cls, label_type, labels):
# ToDo functools.update_wrapper
def wrapper(test):
update_attrs(test, label_type, labels)
return test

return wrapper

@classmethod
def register(cls):
if cls not in plugin_manager.get_plugins():
plugin_manager.register(cls)

@classmethod
def unregister(cls):
if cls in plugin_manager.get_plugins():
plugin_manager.unregister(plugin=cls)


DecoratorsHelper.register()


class Allure(Plugin):
configSection = 'allure'
commandLineSwitch = (None, "allure", "Generate an Allure report")

def __init__(self, *args, **kwargs):
super(Allure, self).__init__(*args, **kwargs)
self._host = host_tag()
self._thread = thread_tag()
self.lifecycle = AllureLifecycle()
self.logger = AllureFileLogger("allure-result")
self.listener = AllureListener(self.lifecycle)

def registerInSubprocess(self, event):
self.unregister_allure_plugins()
event.pluginClasses.append(self.__class__)

def startSubprocess(self, event):
self.register_allure_plugins()

def stopSubprocess(self, event):
self.unregister_allure_plugins()

def register_allure_plugins(self):
plugin_manager.register(self.listener)
plugin_manager.register(self.logger)

def unregister_allure_plugins(self):
plugin_manager.unregister(plugin=self.listener)
plugin_manager.unregister(plugin=self.logger)

def is_registered(self):
return all([plugin_manager.is_registered(self.listener),
plugin_manager.is_registered(self.logger)])

def startTestRun(self, event):
self.register_allure_plugins()

def afterTestRun(self, event):
self.unregister_allure_plugins()

def startTest(self, event):
if self.is_registered():
with self.lifecycle.schedule_test_case() as test_result:
test_result.name = name(event)
test_result.start = timestamp_millis(event.startTime)
test_result.fullName = fullname(event)
test_result.testCaseId = md5(test_result.fullName)
test_result.historyId = md5(event.test.id())
test_result.labels.extend(labels(event.test))
test_result.labels.append(Label(name=LabelType.HOST, value=self._host))
test_result.labels.append(Label(name=LabelType.THREAD, value=self._thread))
test_result.labels.append(Label(name=LabelType.FRAMEWORK, value='nose2'))
test_result.labels.append(Label(name=LabelType.LANGUAGE, value=platform_label()))
test_result.parameters = params(event)

def stopTest(self, event):
if self.is_registered():
with self.lifecycle.update_test_case() as test_result:
test_result.stop = timestamp_millis(event.stopTime)
self.lifecycle.write_test_case()

def testOutcome(self, event):
if self.is_registered():
with self.lifecycle.update_test_case() as test_result:
if event.outcome == result.PASS and event.expected:
test_result.status = Status.PASSED
elif event.outcome == result.PASS and not event.expected:
test_result.status = Status.PASSED
test_result.statusDetails = StatusDetails(message="test passes unexpectedly")
elif event.outcome == result.FAIL and not event.expected:
test_result.status = Status.FAILED
test_result.statusDetails = status_details(event)
elif event.outcome == result.ERROR:
test_result.status = Status.BROKEN
test_result.statusDetails = status_details(event)
elif event.outcome == result.SKIP:
test_result.status = Status.SKIPPED
test_result.statusDetails = status_details(event)
# Todo default status and other cases
# elif event.outcome == result.FAIL and event.expected:
# pass
# self.skipped += 1
# skipped = ET.SubElement(testcase, 'skipped')
# skipped.set('message', 'expected test failure')
# skipped.text = msg
98 changes: 98 additions & 0 deletions allure-nose2/src/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from traceback import format_exception_only
from allure_commons.model2 import StatusDetails, Label
from allure_commons.model2 import Parameter
from allure_commons.utils import represent
from nose2 import util
import inspect

# ToDo move to commons
ALLURE_LABELS = [
'epic',
'feature',
'story',
]


def timestamp_millis(timestamp):
return int(timestamp * 1000)


def status_details(event):
message, trace = None, None
if event.exc_info:
exc_type, value, _ = event.exc_info
message = '\n'.join(format_exception_only(exc_type, value)) if exc_type or value else None
trace = ''.join(util.exc_info_to_string(event.exc_info, event.test))
elif event.reason:
message = event.reason

if message or trace:
return StatusDetails(message=message, trace=trace)


def update_attrs(test, name, values):
if type(values) in (list, tuple, str) and name.isidentifier():
attrib = getattr(test, name, values)
if attrib and attrib != values:
attrib = sum(
[tuple(i) if type(i) in (tuple, list) else (i,) for i in (attrib, values)],
()
)
setattr(test, name, attrib)


def labels(test):

def _get_attrs(obj, keys):
pairs = set()
for key in keys:
values = getattr(obj, key, ())
for value in (values,) if type(values) == str else values:
pairs.add((key, value))
return pairs

keys = ALLURE_LABELS
pairs = _get_attrs(test, keys)

if hasattr(test, "_testFunc"):
pairs.update(_get_attrs(test._testFunc, keys))
elif hasattr(test, "_testMethodName"):
test_method = getattr(test, test._testMethodName)
pairs.update(_get_attrs(test_method, keys))
return [Label(name=name, value=value) for name, value in pairs]


def name(event):
full_name = fullname(event)
return full_name.split(".")[-1]


def fullname(event):
if hasattr(event.test, "_testFunc"):
test_module = event.test._testFunc.__module__
test_name = event.test._testFunc.__name__
return "{module}.{name}".format(module=test_module, name=test_name)
test_id = event.test.id()
return test_id.split(":")[0]


def params(event):
def _params(names, values):
return [Parameter(name=name, value=represent(value)) for name, value in zip(names, values)]

test_id = event.test.id()

if len(test_id.split("\n")) > 1:
if hasattr(event.test, "_testFunc"):
wrapper_arg_spec = inspect.getfullargspec(event.test._testFunc)
arg_set, obj = wrapper_arg_spec.defaults
test_arg_spec = inspect.getfullargspec(obj)
args = test_arg_spec.args
return _params(args, arg_set)
elif hasattr(event.test, "_testMethodName"):
method = getattr(event.test, event.test._testMethodName)
wrapper_arg_spec = inspect.getfullargspec(method)
obj, arg_set = wrapper_arg_spec.defaults
test_arg_spec = inspect.getfullargspec(obj)
args = test_arg_spec.args
return _params(args[1:], arg_set)
Empty file added allure-nose2/test/__init__.py
Empty file.
29 changes: 29 additions & 0 deletions allure-nose2/test/example_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import sys
import types
from importlib import util
from doctest import script_from_examples
from nose2 import events


class CurrentExample(events.Plugin):
commandLineSwitch = (None, "current-example", "Method docstring to module")

def __init__(self, *args, **kwargs):
super(CurrentExample, self).__init__(*args, **kwargs)
self._current_docstring = ""

def startTest(self, event):
if hasattr(event.test, "_testFunc"):
self._current_docstring = event.test._testFunc.__doc__
else:
self._current_docstring = event.test._testMethodDoc

def get_example_module(self):
module = types.ModuleType("stub")
if self._current_docstring:
code = script_from_examples(self._current_docstring)
spec = util.spec_from_loader("example_module", origin="example_module", loader=None)
module = util.module_from_spec(spec)
exec(code, module.__dict__)
sys.modules['example_module'] = module
return module
Loading