Skip to content

Commit 3e7945f

Browse files
author
Yury Krasouski
authored
Merge pull request #52 from ramriddick/xdist_fix
Xdist fix for issue #29
2 parents 6de27cd + 26bf400 commit 3e7945f

File tree

7 files changed

+89
-55
lines changed

7 files changed

+89
-55
lines changed

README.rst

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -69,35 +69,39 @@ Examples
6969

7070
For logging of the test item flow to Report Portal, please, use the python
7171
logging handler provided by plugin like bellow:
72-
72+
in conftest.py:
7373
.. code-block:: python
7474
75-
import logging
76-
# Import Report Portal logger and handler to the test module.
77-
from pytest_reportportal import RPLogger, RPLogHandler
78-
# Setting up a logging.
79-
logging.setLoggerClass(RPLogger)
80-
logger = logging.getLogger(__name__)
81-
logger.setLevel(logging.DEBUG)
82-
# Create handler for Report Portal.
83-
rp_handler = RPLogHandler()
84-
# Set INFO level for Report Portal handler.
85-
rp_handler.setLevel(logging.INFO)
86-
# Add handler to the logger.
87-
logger.addHandler(rp_handler)
88-
75+
@pytest.fixture(scope="function")
76+
def rp_logger(request):
77+
import logging
78+
# Import Report Portal logger and handler to the test module.
79+
from pytest_reportportal import RPLogger, RPLogHandler
80+
# Setting up a logging.
81+
logging.setLoggerClass(RPLogger)
82+
logger = logging.getLogger(__name__)
83+
logger.setLevel(logging.DEBUG)
84+
# Create handler for Report Portal.
85+
rp_handler = RPLogHandler(request.node.config.py_test_service)
86+
# Set INFO level for Report Portal handler.
87+
rp_handler.setLevel(logging.INFO)
88+
# Add handler to the logger.
89+
logger.addHandler(rp_handler)
90+
return logger
91+
in tests:
92+
.. code-block:: python
8993
9094
# In this case only INFO messages will be sent to the Report Portal.
91-
def test_one():
92-
logger.info("Case1. Step1")
95+
def test_one(rp_logger):
96+
rp_logger.info("Case1. Step1")
9397
x = "this"
94-
logger.info("x is: %s", x)
98+
rp_logger.info("x is: %s", x)
9599
assert 'h' in x
96100
97101
# Message with an attachment.
98102
import subprocess
99103
free_memory = subprocess.check_output("free -h".split())
100-
logger.info(
104+
rp_logger.info(
101105
"Case1. Memory consumption",
102106
attachment={
103107
"name": "free_memory.txt",
@@ -107,7 +111,7 @@ logging handler provided by plugin like bellow:
107111
)
108112
109113
# This debug message will not be sent to the Report Portal.
110-
logger.debug("Case1. Debug message")
114+
rp_logger.debug("Case1. Debug message")
111115
112116
Plugin can report doc-strings of tests as :code:`descriptions`:
113117

pytest_reportportal/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
from .rp_logging import RPLogger, RPLogHandler
22

33
__all__ = ['RPLogger', 'RPLogHandler']
4+
5+
LAUNCH_WAIT_TIMEOUT = 10

pytest_reportportal/listener.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import pytest
33
import logging
44

5-
from .service import PyTestService
65

76
try:
87
# This try/except can go away once we support pytest >= 3.3
@@ -14,16 +13,17 @@
1413

1514

1615
class RPReportListener(object):
17-
def __init__(self, log_level=logging.NOTSET):
16+
def __init__(self, py_test_service, log_level=logging.NOTSET):
1817
# Test Item result
18+
self.PyTestService = py_test_service
1919
self.result = None
2020
self._log_level = log_level
2121
if PYTEST_HAS_LOGGING_PLUGIN:
2222
self._log_handler = RPLogHandler(log_level, filter_reportportal_client_logs=True)
2323

2424
@pytest.hookimpl(hookwrapper=True)
2525
def pytest_runtest_protocol(self, item):
26-
PyTestService.start_pytest_item(item)
26+
self.PyTestService.start_pytest_item(item)
2727
if PYTEST_HAS_LOGGING_PLUGIN:
2828
# This check can go away once we support pytest >= 3.3
2929
with patching_logger_class():
@@ -32,14 +32,14 @@ def pytest_runtest_protocol(self, item):
3232
yield
3333
else:
3434
yield
35-
PyTestService.finish_pytest_item(self.result or 'SKIPPED')
35+
self.PyTestService.finish_pytest_item(self.result or 'SKIPPED')
3636

3737
@pytest.hookimpl(hookwrapper=True)
3838
def pytest_runtest_makereport(self):
3939
report = (yield).get_result()
4040

4141
if report.longrepr:
42-
PyTestService.post_log(
42+
self.PyTestService.post_log(
4343
# Used for support python 2.7
4444
cgi.escape(report.longreprtext),
4545
loglevel='ERROR',

pytest_reportportal/plugin.py

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
# and/or modify it under the terms of the GPL licence
33

44
import logging
5-
6-
from .service import PyTestService
5+
import dill as pickle
6+
import pytest
7+
import time
8+
from pytest_reportportal import LAUNCH_WAIT_TIMEOUT
9+
from .service import PyTestServiceClass
710
from .listener import RPReportListener
811

912
try:
@@ -14,24 +17,48 @@
1417
PYTEST_HAS_LOGGING_PLUGIN = False
1518

1619

20+
def is_master(config):
21+
"""
22+
True if the code running the given pytest.config object is running in a xdist master
23+
node or not running xdist at all.
24+
"""
25+
return not hasattr(config, 'slaveinput')
26+
27+
28+
@pytest.mark.optionalhook
29+
def pytest_configure_node(node):
30+
node.slaveinput['py_test_service'] = pickle.dumps(node.config.py_test_service)
31+
32+
1733
def pytest_sessionstart(session):
1834
if session.config.getoption('--collect-only', default=False) is True:
1935
return
2036

21-
PyTestService.init_service(
22-
project=session.config.getini('rp_project'),
23-
endpoint=session.config.getini('rp_endpoint'),
24-
uuid=session.config.getini('rp_uuid'),
25-
log_batch_size=int(session.config.getini('rp_log_batch_size')),
26-
ignore_errors=bool(session.config.getini('rp_ignore_errors')),
27-
ignored_tags=session.config.getini('rp_ignore_tags'),
28-
)
37+
if is_master(session.config):
38+
session.config.py_test_service.init_service(
39+
project=session.config.getini('rp_project'),
40+
endpoint=session.config.getini('rp_endpoint'),
41+
uuid=session.config.getini('rp_uuid'),
42+
log_batch_size=int(session.config.getini('rp_log_batch_size')),
43+
ignore_errors=bool(session.config.getini('rp_ignore_errors')),
44+
ignored_tags=session.config.getini('rp_ignore_tags'),
45+
)
46+
47+
session.config.py_test_service.start_launch(
48+
session.config.option.rp_launch,
49+
tags=session.config.getini('rp_launch_tags'),
50+
description=session.config.getini('rp_launch_description'),
51+
)
52+
if session.config.pluginmanager.hasplugin('xdist'):
53+
wait_launch(session.config.py_test_service.RP.rp_client)
2954

30-
PyTestService.start_launch(
31-
session.config.option.rp_launch,
32-
tags=session.config.getini('rp_launch_tags'),
33-
description=session.config.option.rp_launch_description,
34-
)
55+
56+
def wait_launch(rp_client):
57+
timeout = time.time() + LAUNCH_WAIT_TIMEOUT
58+
while not rp_client.launch_id:
59+
if time.time() > timeout:
60+
raise Exception("Launch not found")
61+
time.sleep(1)
3562

3663

3764
def pytest_sessionfinish(session):
@@ -40,7 +67,8 @@ def pytest_sessionfinish(session):
4067

4168
# FixMe: currently method of RP api takes the string parameter
4269
# so it is hardcoded
43-
PyTestService.finish_launch(status='RP_Launch')
70+
if is_master(session.config):
71+
session.config.py_test_service.finish_launch(status='RP_Launch')
4472

4573

4674
def pytest_configure(config):
@@ -49,30 +77,33 @@ def pytest_configure(config):
4977
if not config.option.rp_launch_description:
5078
config.option.rp_launch_description = config.getini('rp_launch_description')
5179

52-
if config.pluginmanager.hasplugin('xdist'):
53-
raise Exception(
54-
"pytest report portal is not compatible with 'xdist' plugin.")
80+
if is_master(config):
81+
config.py_test_service = PyTestServiceClass()
82+
else:
83+
config.py_test_service = pickle.loads(config.slaveinput['py_test_service'])
84+
config.py_test_service.RP.listener.start()
5585

5686
# set Pytest_Reporter and configure it
5787

5888
if PYTEST_HAS_LOGGING_PLUGIN:
5989
# This check can go away once we support pytest >= 3.3
6090
try:
6191
config._reporter = RPReportListener(
92+
config.py_test_service,
6293
_pytest.logging.get_actual_log_level(config, 'rp_log_level')
6394
)
6495
except TypeError:
6596
# No log level set either in INI or CLI
66-
config._reporter = RPReportListener()
97+
config._reporter = RPReportListener(config.py_test_service)
6798
else:
68-
config._reporter = RPReportListener()
99+
config._reporter = RPReportListener(config.py_test_service)
69100

70101
if hasattr(config, '_reporter'):
71102
config.pluginmanager.register(config._reporter)
72103

73104

74105
def pytest_unconfigure(config):
75-
PyTestService.terminate_service()
106+
config.py_test_service.terminate_service()
76107

77108
if hasattr(config, '_reporter'):
78109
reporter = config._reporter

pytest_reportportal/rp_logging.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
from functools import wraps
55
from six import PY2
66

7-
from .service import PyTestService
8-
97

108
class RPLogger(logging.getLoggerClass()):
119
def __init__(self, name, level=0):
@@ -62,10 +60,11 @@ class RPLogHandler(logging.Handler):
6260
}
6361
_sorted_levelnos = sorted(_loglevel_map.keys(), reverse=True)
6462

65-
def __init__(self, level=logging.NOTSET,
63+
def __init__(self, py_test_service, level=logging.NOTSET,
6664
filter_reportportal_client_logs=False):
6765
super(RPLogHandler, self).__init__(level)
6866
self.filter_reportportal_client_logs = filter_reportportal_client_logs
67+
self.py_test_service = py_test_service
6968

7069
def filter(self, record):
7170
if self.filter_reportportal_client_logs is False:
@@ -90,7 +89,7 @@ def emit(self, record):
9089
if level <= record.levelno:
9190
break
9291

93-
return PyTestService.post_log(
92+
return self.py_test_service.post_log(
9493
msg,
9594
loglevel=self._loglevel_map[level],
9695
attachment=record.__dict__.get('attachment', None),

pytest_reportportal/service.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,3 @@ def _get_description(test_item):
179179
except AttributeError:
180180
# doctest has no `function` attribute
181181
return test_item.reportinfo()[2]
182-
183-
184-
PyTestService = PyTestServiceClass()

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ def read_file(fname):
1212

1313

1414
requirements = [
15-
'reportportal-client>=3.0.0',
15+
'reportportal-client>=3.1.0',
1616
'pytest>=3.0.7',
1717
'six>=1.10.0',
18+
'dill>=0.2.7.1',
1819
]
1920

2021

0 commit comments

Comments
 (0)