Skip to content

Commit b64f195

Browse files
authored
Merge pull request #35 from lebovski/RPPT-001
RPPT-001 | Rewrite listener, fix rp_logger. Make code more pythonic.
2 parents e2949f6 + 0d5f82c commit b64f195

File tree

7 files changed

+248
-246
lines changed

7 files changed

+248
-246
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Created by .ignore support plugin (hsz.mobi)
2+
.idea

pytest_reportportal/__init__.py

Lines changed: 2 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,3 @@
1-
import sys
2-
import logging
1+
from .rp_logging import RPLogger, RPLogHandler
32

4-
from .service import PyTestService
5-
6-
7-
class RPLogger(logging.getLoggerClass()):
8-
9-
def __init__(self, name, level=0):
10-
super(RPLogger, self).__init__(name, level=level)
11-
12-
def _log(self, level, msg, args, exc_info=None, extra=None,
13-
attachment=None):
14-
"""
15-
Low-level logging routine which creates a LogRecord and then calls
16-
all the handlers of this logger to handle the record.
17-
"""
18-
if logging._srcfile:
19-
# IronPython doesn't track Python frames, so findCaller raises an
20-
# exception on some versions of IronPython. We trap it here so that
21-
# IronPython can use logging.
22-
try:
23-
fn, lno, func = self.findCaller()
24-
except ValueError:
25-
fn, lno, func = "(unknown file)", 0, "(unknown function)"
26-
else:
27-
fn, lno, func = "(unknown file)", 0, "(unknown function)"
28-
29-
if exc_info and not isinstance(exc_info, tuple):
30-
exc_info = sys.exc_info()
31-
32-
record = self.makeRecord(
33-
self.name, level, fn, lno, msg, args, exc_info, func, extra
34-
)
35-
record.attachment = attachment
36-
self.handle(record)
37-
38-
39-
class RPLogHandler(logging.Handler):
40-
41-
# Map loglevel codes from `logging` module to ReportPortal text names:
42-
_loglevel_map = {
43-
logging.NOTSET: "TRACE",
44-
logging.DEBUG: "DEBUG",
45-
logging.INFO: "INFO",
46-
logging.WARNING: "WARN",
47-
logging.ERROR: "ERROR",
48-
logging.CRITICAL: "ERROR",
49-
}
50-
_sorted_levelnos = sorted(_loglevel_map.keys(), reverse=True)
51-
52-
def __init__(self, level=logging.NOTSET):
53-
super(RPLogHandler, self).__init__(level)
54-
55-
def emit(self, record):
56-
try:
57-
msg = self.format(record)
58-
except (KeyboardInterrupt, SystemExit):
59-
raise
60-
except:
61-
self.handleError(record)
62-
63-
for level in self._sorted_levelnos:
64-
if level <= record.levelno:
65-
break
66-
67-
return PyTestService.post_log(
68-
msg,
69-
loglevel=self._loglevel_map[level],
70-
attachment=record.__dict__.get("attachment", None),
71-
)
3+
__all__ = ['RPLogger', 'RPLogHandler']

pytest_reportportal/listener.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import cgi
2+
import pytest
3+
4+
from .service import PyTestService
5+
6+
7+
class RPReportListener(object):
8+
def __init__(self):
9+
# Identifier if TestItem is called:
10+
# if setup is failed, pytest will NOT call
11+
# TestItem and Result will not reported!
12+
self.called = False
13+
14+
# Test Item result
15+
self.result = None
16+
17+
@pytest.hookimpl(hookwrapper=True)
18+
def pytest_runtest_protocol(self, item):
19+
PyTestService.start_pytest_item(item)
20+
yield
21+
item_result = self.result if self.called else 'SKIPPED'
22+
PyTestService.finish_pytest_item(item_result)
23+
self.called = False
24+
25+
@pytest.hookimpl(hookwrapper=True)
26+
def pytest_runtest_makereport(self):
27+
report = (yield).get_result()
28+
29+
if report.longrepr:
30+
PyTestService.post_log(
31+
# Used for support python 2.7
32+
cgi.escape(report.longreprtext),
33+
loglevel='ERROR',
34+
)
35+
36+
if report.when == 'call':
37+
self.called = True
38+
39+
if report.passed:
40+
item_result = 'PASSED'
41+
elif report.failed:
42+
item_result = 'FAILED'
43+
else:
44+
item_result = 'SKIPPED'
45+
46+
self.result = item_result

pytest_reportportal/plugin.py

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

44
import logging
5-
import cgi
6-
7-
import pytest
85

96
from .service import PyTestService
10-
11-
12-
class RP_Report_Listener(object):
13-
14-
# Identifier if TestItem is called:
15-
# if setup is failed, pytest will NOT call
16-
# TestItem and Result will not reported!
17-
called = None
18-
# Test Item result
19-
result = None
20-
21-
@pytest.mark.hookwrapper
22-
def pytest_runtest_makereport(self, item, call):
23-
report = (yield).get_result()
24-
25-
if report.when == "setup":
26-
# when function pytest_setup is called,
27-
# test item session will be started in RP
28-
PyTestService.start_pytest_item(item)
29-
30-
if report.longrepr:
31-
PyTestService.post_log(
32-
cgi.escape(report.longreprtext),
33-
loglevel='ERROR',
34-
)
35-
36-
if report.when == "call":
37-
self.called = True
38-
if report.passed:
39-
item_result = "PASSED"
40-
elif report.failed:
41-
item_result = "FAILED"
42-
else:
43-
item_result = "SKIPPED"
44-
45-
self.result = item_result
46-
47-
if report.when == "teardown":
48-
# If item is called, result of TestItem is reported
49-
if self.called is True:
50-
item_result = self.result
51-
else:
52-
# If setup - failed or skipped,
53-
# the TestItem will reported as SKIPPED
54-
item_result = "SKIPPED"
55-
PyTestService.finish_pytest_item(item_result)
56-
self.called = None
7+
from .listener import RPReportListener
578

589

5910
def pytest_sessionstart(session):
6011
PyTestService.init_service(
61-
project=session.config.getini("rp_project"),
62-
endpoint=session.config.getini("rp_endpoint"),
63-
uuid=session.config.getini("rp_uuid"),
64-
log_batch_size=int(session.config.getini("rp_log_batch_size")),
65-
ignore_errors=bool(session.config.getini("rp_ignore_errors")),
66-
ignored_tags=session.config.getini("rp_ignore_tags"),
12+
project=session.config.getini('rp_project'),
13+
endpoint=session.config.getini('rp_endpoint'),
14+
uuid=session.config.getini('rp_uuid'),
15+
log_batch_size=int(session.config.getini('rp_log_batch_size')),
16+
ignore_errors=bool(session.config.getini('rp_ignore_errors')),
17+
ignored_tags=session.config.getini('rp_ignore_tags'),
6718
)
6819

6920
PyTestService.start_launch(
7021
session.config.option.rp_launch,
71-
tags=session.config.getini("rp_launch_tags"),
72-
description=session.config.getini("rp_launch_description"),
22+
tags=session.config.getini('rp_launch_tags'),
23+
description=session.config.getini('rp_launch_description'),
7324
)
7425

7526

76-
def pytest_sessionfinish(session):
27+
def pytest_sessionfinish():
7728
# FixMe: currently method of RP api takes the string parameter
7829
# so it is hardcoded
79-
PyTestService.finish_launch(status="RP_Launch")
30+
PyTestService.finish_launch(status='RP_Launch')
8031

8132

8233
def pytest_configure(config):
8334
if not config.option.rp_launch:
84-
config.option.rp_launch = config.getini("rp_launch")
35+
config.option.rp_launch = config.getini('rp_launch')
8536

8637
# set Pytest_Reporter and configure it
87-
config._reporter = RP_Report_Listener()
38+
config._reporter = RPReportListener()
8839

89-
if hasattr(config, "_reporter"):
40+
if hasattr(config, '_reporter'):
9041
config.pluginmanager.register(config._reporter)
9142

9243

9344
def pytest_unconfigure(config):
9445
PyTestService.terminate_service()
9546

96-
if hasattr(config, "_reporter"):
47+
if hasattr(config, '_reporter'):
9748
reporter = config._reporter
9849
del config._reporter
9950
config.pluginmanager.unregister(reporter)
100-
logging.debug("RP is unconfigured")
51+
logging.debug('RP is unconfigured')
10152

10253

10354
def pytest_addoption(parser):
104-
group = parser.getgroup("reporting")
55+
group = parser.getgroup('reporting')
10556
group.addoption(
106-
"--rp-launch",
107-
action="store",
108-
dest="rp_launch",
109-
help="Launch name (overrides rp_launch config option)")
57+
'--rp-launch',
58+
action='store',
59+
dest='rp_launch',
60+
help='Launch name (overrides rp_launch config option)')
11061

11162
parser.addini(
112-
"rp_uuid",
113-
help="UUID")
63+
'rp_uuid',
64+
help='UUID')
11465

11566
parser.addini(
116-
"rp_endpoint",
117-
help="Server endpoint")
67+
'rp_endpoint',
68+
help='Server endpoint')
11869

11970
parser.addini(
120-
"rp_project",
121-
help="Project name")
71+
'rp_project',
72+
help='Project name')
12273

12374
parser.addini(
124-
"rp_launch",
125-
default="Pytest Launch",
126-
help="Launch name")
75+
'rp_launch',
76+
default='Pytest Launch',
77+
help='Launch name')
12778

12879
parser.addini(
129-
"rp_launch_tags",
130-
type="args",
131-
help="Launch tags, i.e Performance Regression")
80+
'rp_launch_tags',
81+
type='args',
82+
help='Launch tags, i.e Performance Regression')
13283

13384
parser.addini(
134-
"rp_launch_description",
135-
default="",
136-
help="Launch description")
85+
'rp_launch_description',
86+
default='',
87+
help='Launch description')
13788

13889
parser.addini(
139-
"rp_log_batch_size",
140-
default="20",
141-
help="Size of batch log requests in async mode")
90+
'rp_log_batch_size',
91+
default='20',
92+
help='Size of batch log requests in async mode')
14293

14394
parser.addini(
144-
"rp_ignore_errors",
95+
'rp_ignore_errors',
14596
default=False,
146-
type="bool",
147-
help="Ignore Report Portal errors (exit otherwise)")
97+
type='bool',
98+
help='Ignore Report Portal errors (exit otherwise)')
14899

149100
parser.addini(
150-
"rp_ignore_tags",
151-
type="args",
152-
help="Ignore specified pytest markers, i.e parametrize")
101+
'rp_ignore_tags',
102+
type='args',
103+
help='Ignore specified pytest markers, i.e parametrize')

pytest_reportportal/rp_logging.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import sys
2+
import logging
3+
4+
from .service import PyTestService
5+
6+
7+
class RPLogger(logging.getLoggerClass()):
8+
def __init__(self, name, level=0):
9+
super(RPLogger, self).__init__(name, level=level)
10+
11+
def _log(self, level, msg, args,
12+
exc_info=None, extra=None, stack_info=False, attachment=None):
13+
"""
14+
Low-level logging routine which creates a LogRecord and then calls
15+
all the handlers of this logger to handle the record.
16+
"""
17+
sinfo = None
18+
if logging._srcfile:
19+
# IronPython doesn't track Python frames, so findCaller raises an
20+
# exception on some versions of IronPython. We trap it here so that
21+
# IronPython can use logging.
22+
try:
23+
fn, lno, func, sinfo = self.findCaller(stack_info)
24+
except ValueError: # pragma: no cover
25+
fn, lno, func = '(unknown file)', 0, '(unknown function)'
26+
else:
27+
fn, lno, func = '(unknown file)', 0, '(unknown function)'
28+
29+
if exc_info and not isinstance(exc_info, tuple):
30+
exc_info = sys.exc_info()
31+
32+
record = self.makeRecord(
33+
self.name, level, fn, lno, msg, args, exc_info, func, extra, sinfo
34+
)
35+
record.attachment = attachment
36+
self.handle(record)
37+
38+
39+
class RPLogHandler(logging.Handler):
40+
# Map loglevel codes from `logging` module to ReportPortal text names:
41+
_loglevel_map = {
42+
logging.NOTSET: 'TRACE',
43+
logging.DEBUG: 'DEBUG',
44+
logging.INFO: 'INFO',
45+
logging.WARNING: 'WARN',
46+
logging.ERROR: 'ERROR',
47+
logging.CRITICAL: 'ERROR',
48+
}
49+
_sorted_levelnos = sorted(_loglevel_map.keys(), reverse=True)
50+
51+
def __init__(self, level=logging.NOTSET):
52+
super(RPLogHandler, self).__init__(level)
53+
54+
def emit(self, record):
55+
msg = ''
56+
57+
try:
58+
msg = self.format(record)
59+
except (KeyboardInterrupt, SystemExit):
60+
raise
61+
except:
62+
self.handleError(record)
63+
64+
for level in self._sorted_levelnos:
65+
if level <= record.levelno:
66+
break
67+
68+
return PyTestService.post_log(
69+
msg,
70+
loglevel=self._loglevel_map[level],
71+
attachment=record.__dict__.get('attachment', None),
72+
)

0 commit comments

Comments
 (0)