Skip to content

Commit b0322bc

Browse files
author
左武辉51085
committed
[ADD] allure-pytest-log plugin
1 parent 7285adf commit b0322bc

File tree

11 files changed

+593
-0
lines changed

11 files changed

+593
-0
lines changed

allure-pytest-log/README.rst

+18
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

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# -*- coding: UTF-8 -*-

allure-pytest-log/setup.py

+58
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="[email protected]",
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

Whitespace-only changes.

allure-pytest-log/src/listener.py

+79
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

+48
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

+35
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

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# -*- coding: UTF-8 -*-

allure-pytest-log/test/conftest.py

+89
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)