Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 68048d4

Browse filesBrowse files
committed
[ADD] allure-pytest-log plugin
1 parent 7285adf commit 68048d4
Copy full SHA for 68048d4

File tree

11 files changed

+593
-0
lines changed
Filter options

11 files changed

+593
-0
lines changed

‎allure-pytest-log/README.rst

Copy file name to clipboard
+18Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Allure With Log Capturing Pytest Plugin
2+
====================
3+
4+
- `Source <https://github.com/allure-framework/allure-python>`_
5+
6+
- `Documentation <https://docs.qameta.io/allure/2.0/>`_
7+
8+
- `Gitter <https://gitter.im/allure-framework/allure-core>`_
9+
10+
11+
Installation and Usage
12+
======================
13+
14+
.. code:: bash
15+
16+
$ pip install allure-pytest-log
17+
$ py.test --allure-capture [--alluredir=%allure_result_folder%] ./tests
18+
$ allure serve %allure_result_folder%

‎allure-pytest-log/__init__.py

Copy file name to clipboard
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# -*- coding: UTF-8 -*-

‎allure-pytest-log/setup.py

Copy file name to clipboard
+58Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import os, sys
2+
from setuptools import setup
3+
from pkg_resources import require, DistributionNotFound, VersionConflict
4+
5+
try:
6+
require('pytest-allure-adaptor')
7+
print("""
8+
You have pytest-allure-adaptor installed.
9+
You need to remove pytest-allure-adaptor from your site-packages
10+
before installing allure-pytest, or conflicts may result.
11+
""")
12+
sys.exit()
13+
except (DistributionNotFound, VersionConflict):
14+
pass
15+
16+
PACKAGE = "allure-pytest-log"
17+
VERSION = "0.1.0"
18+
19+
classifiers = [
20+
'Development Status :: 5 - Production/Stable',
21+
'Framework :: Pytest',
22+
'Intended Audience :: Developers',
23+
'License :: OSI Approved :: Apache Software License',
24+
'Topic :: Software Development :: Quality Assurance',
25+
'Topic :: Software Development :: Testing',
26+
]
27+
28+
install_requires = [
29+
"allure-pytest>=2.4.1",
30+
"allure-python-commons>=2.4.1"
31+
]
32+
33+
34+
def read(fname):
35+
return open(os.path.join(os.path.dirname(__file__), fname)).read()
36+
37+
38+
def main():
39+
setup(
40+
name=PACKAGE,
41+
version=VERSION,
42+
description="Allure pytest integration with stdout capturing",
43+
url="https://github.com/allure-framework/allure-python-log",
44+
author="WuhuiZuo",
45+
author_email="wuhuizuo@126.com",
46+
license="Apache-2.0",
47+
classifiers=classifiers,
48+
keywords="allure reporting pytest output_capture",
49+
long_description=read('README.rst'),
50+
packages=["allure_pytest_log"],
51+
package_dir={"allure_pytest_log": "src"},
52+
entry_points={"pytest11": ["allure_pytest_log = allure_pytest_log.plugin"]},
53+
install_requires=install_requires
54+
)
55+
56+
57+
if __name__ == '__main__':
58+
main()

‎allure-pytest-log/src/__init__.py

Copy file name to clipboardExpand all lines: allure-pytest-log/src/__init__.py
Whitespace-only changes.

‎allure-pytest-log/src/listener.py

Copy file name to clipboard
+79Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import pytest
2+
import allure_commons
3+
from allure_commons.types import AttachmentType
4+
from allure_pytest.listener import ItemCache
5+
from .utils import Tee
6+
7+
8+
class AllureLogListener(object):
9+
10+
def __init__(self, allure_listener=None):
11+
self.allure_listener = allure_listener
12+
self.modify_allure_listener(self.allure_listener)
13+
self._cache = allure_listener._cache
14+
self._tee_cache = TeeCache()
15+
16+
def modify_allure_listener(self, listener):
17+
origin_start_before_fixture = listener.allure_logger.start_before_fixture
18+
origin_stop_before_fixture = listener.allure_logger.stop_before_fixture
19+
origin_start_after_fixture = listener.allure_logger.start_after_fixture
20+
origin_stop_after_fixture = listener.allure_logger.stop_after_fixture
21+
22+
def start_before_fixture(parent_uuid, uuid, fixture):
23+
origin_start_before_fixture(parent_uuid, uuid, fixture)
24+
self.start_tee(uuid)
25+
26+
def stop_before_fixture(uuid, **kwargs):
27+
self.finish_tee(uuid, 'fixture log')
28+
origin_stop_before_fixture(uuid, **kwargs)
29+
30+
def start_after_fixture(parent_uuid, uuid, fixture):
31+
origin_start_after_fixture(parent_uuid, uuid, fixture)
32+
self.start_tee(uuid)
33+
34+
def stop_after_fixture(uuid, **kwargs):
35+
self.finish_tee(uuid, 'fixture[after] log')
36+
origin_stop_after_fixture(uuid, **kwargs)
37+
38+
listener.allure_logger.start_before_fixture = start_before_fixture
39+
listener.allure_logger.stop_before_fixture = stop_before_fixture
40+
listener.allure_logger.start_after_fixture = start_after_fixture
41+
listener.allure_logger.stop_after_fixture = stop_after_fixture
42+
43+
def start_tee(self, uuid):
44+
tee = self._tee_cache.set(uuid)
45+
tee.start()
46+
47+
def finish_tee(self, uuid, attach_name='log'):
48+
tee = self._tee_cache.pop(uuid)
49+
if not tee:
50+
return None
51+
try:
52+
self.allure_listener.allure_logger.attach_data(uuid,
53+
body=tee.getvalue(),
54+
name=attach_name,
55+
attachment_type=AttachmentType.TEXT)
56+
finally:
57+
tee.close()
58+
59+
@allure_commons.hookimpl(hookwrapper=True)
60+
def start_step(self, uuid, title, params):
61+
yield
62+
self.start_tee(uuid)
63+
64+
@allure_commons.hookimpl(hookwrapper=True)
65+
def stop_step(self, uuid, exc_type, exc_val, exc_tb):
66+
self.finish_tee(uuid, 'step log')
67+
yield
68+
69+
@pytest.hookimpl(hookwrapper=True)
70+
def pytest_runtest_call(self, item):
71+
uuid = self._cache.get(item.nodeid)
72+
self.start_tee(uuid)
73+
yield
74+
self.finish_tee(uuid, 'test log')
75+
76+
77+
class TeeCache(ItemCache):
78+
def set(self, _id):
79+
return self._items.setdefault(str(_id), Tee())

‎allure-pytest-log/src/plugin.py

Copy file name to clipboard
+48Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import allure_commons
2+
from allure_commons.logger import AllureFileLogger
3+
from allure_pytest.listener import AllureListener
4+
from .listener import AllureLogListener
5+
6+
7+
def _enable_allure_capture(config):
8+
allure_listener = config.pluginmanager.getplugin('AllureListener')
9+
10+
if not allure_listener:
11+
# registry allure-pytest(dependency) plugin first
12+
report_dir = config.option.allure_report_dir or 'reports'
13+
clean = config.option.clean_alluredir
14+
15+
allure_listener = AllureListener(config)
16+
config.pluginmanager.register(allure_listener)
17+
allure_commons.plugin_manager.register(allure_listener)
18+
config.add_cleanup(cleanup_factory(allure_listener))
19+
20+
file_logger = AllureFileLogger(report_dir, clean)
21+
allure_commons.plugin_manager.register(file_logger)
22+
config.add_cleanup(cleanup_factory(file_logger))
23+
24+
allure_log_listener = AllureLogListener(allure_listener)
25+
config.pluginmanager.register(allure_log_listener)
26+
allure_commons.plugin_manager.register(allure_log_listener)
27+
config.add_cleanup(cleanup_factory(allure_log_listener))
28+
29+
30+
def pytest_addoption(parser):
31+
parser.getgroup("reporting").addoption('--allure-capture',
32+
action="store_true",
33+
dest="allure_capture",
34+
help="Capture standard output to Allure report")
35+
36+
37+
def cleanup_factory(plugin):
38+
def clean_up():
39+
name = allure_commons.plugin_manager.get_name(plugin)
40+
allure_commons.plugin_manager.unregister(name=name)
41+
42+
return clean_up
43+
44+
45+
def pytest_configure(config):
46+
allure_capture = config.option.allure_capture
47+
if allure_capture:
48+
_enable_allure_capture(config)

‎allure-pytest-log/src/utils.py

Copy file name to clipboard
+35Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# -*- coding: UTF-8 -*-
2+
import io
3+
import sys
4+
5+
6+
class Tee(object):
7+
def __init__(self):
8+
self.memory = io.StringIO()
9+
self.origin_stdout = None
10+
11+
def start(self):
12+
self.origin_stdout, sys.stdout = sys.stdout, self
13+
14+
def close(self):
15+
if self.origin_stdout:
16+
sys.stdout = self.origin_stdout
17+
self.flush()
18+
19+
def getvalue(self, *args, **kwargs):
20+
return self.memory.getvalue(*args, **kwargs)
21+
22+
def write(self, data):
23+
self.memory.write(data)
24+
if self.origin_stdout:
25+
self.origin_stdout.write(data)
26+
27+
def flush(self):
28+
self.memory.seek(0)
29+
30+
def __enter__(self):
31+
self.start()
32+
return self
33+
34+
def __exit__(self, exc_type, exc_val, exc_tb):
35+
self.close()

‎allure-pytest-log/test/__init__.py

Copy file name to clipboard
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# -*- coding: UTF-8 -*-

‎allure-pytest-log/test/conftest.py

Copy file name to clipboard
+89Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
from __future__ import print_function
2+
import pytest
3+
import os
4+
import sys
5+
import subprocess
6+
import shlex
7+
import hashlib
8+
from inspect import getmembers, isfunction
9+
from allure_commons_test.report import AllureReport
10+
from allure_commons.utils import thread_tag
11+
12+
13+
with open("debug-runner", "w") as debugfile:
14+
# overwrite debug-runner file with an empty one
15+
print("New session", file=debugfile)
16+
17+
18+
def _get_hash(input):
19+
if sys.version_info < (3, 0):
20+
data = bytes(input)
21+
else:
22+
data = bytes(input, 'utf8')
23+
return hashlib.md5(data).hexdigest()
24+
25+
26+
@pytest.fixture(scope='function', autouse=True)
27+
def inject_matchers(doctest_namespace):
28+
import hamcrest
29+
for name, function in getmembers(hamcrest, isfunction):
30+
doctest_namespace[name] = function
31+
32+
from allure_commons_test import container, label, report, result
33+
for module in [container, label, report, result]:
34+
for name, function in getmembers(module, isfunction):
35+
doctest_namespace[name] = function
36+
37+
38+
def _runner(allure_dir, module, *extra_params):
39+
extra_params = ' '.join(extra_params)
40+
cmd = shlex.split('%s -m pytest --allure-capture --alluredir=%s %s %s' % (sys.executable, allure_dir, extra_params, module),
41+
posix=False if os.name == "nt" else True)
42+
with open("debug-runner", "a") as debugfile:
43+
try:
44+
subprocess.check_output(cmd, stderr = subprocess.STDOUT)
45+
except subprocess.CalledProcessError as e:
46+
# Save to debug file errors on execution (includes pytest failing tests)
47+
print(e.output, file=debugfile)
48+
49+
50+
@pytest.fixture(scope='module')
51+
def allure_report_with_params(request, tmpdir_factory):
52+
module = request.module.__file__
53+
tmpdir = tmpdir_factory.mktemp('data')
54+
55+
def run_with_params(*params, **kwargs):
56+
cache = kwargs.get("cache", True)
57+
key = _get_hash('{thread}{module}{param}'.format(thread=thread_tag(), module=module, param=''.join(params)))
58+
if not request.config.cache.get(key, False):
59+
_runner(tmpdir.strpath, module, *params)
60+
if cache:
61+
request.config.cache.set(key, True)
62+
63+
def clear_cache():
64+
request.config.cache.set(key, False)
65+
request.addfinalizer(clear_cache)
66+
67+
return AllureReport(tmpdir.strpath)
68+
return run_with_params
69+
70+
71+
@pytest.fixture(scope='module')
72+
def allure_report(request, tmpdir_factory):
73+
module = request.module.__file__
74+
tmpdir = tmpdir_factory.mktemp('data')
75+
_runner(tmpdir.strpath, module)
76+
return AllureReport(tmpdir.strpath)
77+
78+
79+
def pytest_collection_modifyitems(items, config):
80+
if config.option.doctestmodules:
81+
items[:] = [item for item in items if item.__class__.__name__ == 'DoctestItem']
82+
83+
84+
def pytest_ignore_collect(path, config):
85+
if sys.version_info.major < 3 and "py3_only" in path.strpath:
86+
return True
87+
88+
if sys.version_info.major > 2 and "py2_only" in path.strpath:
89+
return True

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.