Skip to content

Commit fb80c13

Browse files
author
alexandr
committed
RPPT-001 | Rewrite listener, fix rp_logger. Make code more pythonic.
1 parent e2949f6 commit fb80c13

File tree

7 files changed

+188
-197
lines changed

7 files changed

+188
-197
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: 1 addition & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1 @@
1-
import sys
2-
import logging
3-
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-
)
1+
from .rp_logging import RPLogger, RPLogHandler

pytest_reportportal/listener.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import html
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+
html.escape(report.longreprtext),
32+
loglevel='ERROR',
33+
)
34+
35+
if report.when == 'call':
36+
self.called = True
37+
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

pytest_reportportal/plugin.py

Lines changed: 16 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,98 +2,49 @@
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)
@@ -103,7 +54,7 @@ def pytest_unconfigure(config):
10354
def pytest_addoption(parser):
10455
group = parser.getgroup("reporting")
10556
group.addoption(
106-
"--rp-launch",
57+
'--rp-launch',
10758
action="store",
10859
dest="rp_launch",
10960
help="Launch name (overrides rp_launch config option)")

pytest_reportportal/rp_logging.py

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

0 commit comments

Comments
 (0)