Skip to content

Commit 99a4a1a

Browse files
authored
Merge pull request #1791 from nicoddemus/ide-integration-1790
Internal adjustments for easier integration with IDEs
2 parents 4ab2e57 + 1a79137 commit 99a4a1a

File tree

6 files changed

+123
-11
lines changed

6 files changed

+123
-11
lines changed

_pytest/junitxml.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,10 @@ def _add_simple(self, kind, message, data=None):
118118

119119
def _write_captured_output(self, report):
120120
for capname in ('out', 'err'):
121-
allcontent = ""
122-
for name, content in report.get_sections("Captured std%s" %
123-
capname):
124-
allcontent += content
125-
if allcontent:
121+
content = getattr(report, 'capstd' + capname)
122+
if content:
126123
tag = getattr(Junit, 'system-' + capname)
127-
self.append(tag(bin_xml_escape(allcontent)))
124+
self.append(tag(bin_xml_escape(content)))
128125

129126
def append_pass(self, report):
130127
self.add_stats('passed')

_pytest/python.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -357,11 +357,13 @@ def _genfunctions(self, name, funcobj):
357357
fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm)
358358

359359
for callspec in metafunc._calls:
360-
subname = "%s[%s]" %(name, callspec.id)
360+
subname = "%s[%s]" % (name, callspec.id)
361361
yield Function(name=subname, parent=self,
362362
callspec=callspec, callobj=funcobj,
363363
fixtureinfo=fixtureinfo,
364-
keywords={callspec.id:True})
364+
keywords={callspec.id:True},
365+
originalname=name,
366+
)
365367

366368

367369
def _marked(func, mark):
@@ -1471,7 +1473,7 @@ class Function(FunctionMixin, pytest.Item, fixtures.FuncargnamesCompatAttr):
14711473
_genid = None
14721474
def __init__(self, name, parent, args=None, config=None,
14731475
callspec=None, callobj=NOTSET, keywords=None, session=None,
1474-
fixtureinfo=None):
1476+
fixtureinfo=None, originalname=None):
14751477
super(Function, self).__init__(name, parent, config=config,
14761478
session=session)
14771479
self._args = args
@@ -1493,6 +1495,12 @@ def __init__(self, name, parent, args=None, config=None,
14931495
self.fixturenames = fixtureinfo.names_closure
14941496
self._initrequest()
14951497

1498+
#: original function name, without any decorations (for example
1499+
#: parametrization adds a ``"[...]"`` suffix to function names).
1500+
#:
1501+
#: .. versionadded:: 3.0
1502+
self.originalname = originalname
1503+
14961504
def _initrequest(self):
14971505
self.funcargs = {}
14981506
if self._isyieldedfunction():

_pytest/runner.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,36 @@ def get_sections(self, prefix):
211211
if name.startswith(prefix):
212212
yield prefix, content
213213

214+
@property
215+
def longreprtext(self):
216+
"""
217+
Read-only property that returns the full string representation
218+
of ``longrepr``.
219+
220+
.. versionadded:: 3.0
221+
"""
222+
tw = py.io.TerminalWriter(stringio=True)
223+
tw.hasmarkup = False
224+
self.toterminal(tw)
225+
exc = tw.stringio.getvalue()
226+
return exc.strip()
227+
228+
@property
229+
def capstdout(self):
230+
"""Return captured text from stdout, if capturing is enabled
231+
232+
.. versionadded:: 3.0
233+
"""
234+
return ''.join(content for (prefix, content) in self.get_sections('Captured stdout'))
235+
236+
@property
237+
def capstderr(self):
238+
"""Return captured text from stderr, if capturing is enabled
239+
240+
.. versionadded:: 3.0
241+
"""
242+
return ''.join(content for (prefix, content) in self.get_sections('Captured stderr'))
243+
214244
passed = property(lambda x: x.outcome == "passed")
215245
failed = property(lambda x: x.outcome == "failed")
216246
skipped = property(lambda x: x.outcome == "skipped")
@@ -276,8 +306,10 @@ def __init__(self, nodeid, location, keywords, outcome,
276306
#: one of 'setup', 'call', 'teardown' to indicate runtest phase.
277307
self.when = when
278308

279-
#: list of (secname, data) extra information which needs to
280-
#: marshallable
309+
#: list of pairs ``(str, str)`` of extra information which needs to
310+
#: marshallable. Used by pytest to add captured text
311+
#: from ``stdout`` and ``stderr``, but may be used by other plugins
312+
#: to add arbitrary information to reports.
281313
self.sections = list(sections)
282314

283315
#: time it took to run just the test

doc/en/writing_plugins.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,7 @@ Reference of objects involved in hooks
632632

633633
.. autoclass:: _pytest.runner.TestReport()
634634
:members:
635+
:inherited-members:
635636

636637
.. autoclass:: _pytest.vendored_packages.pluggy._CallOutcome()
637638
:members:

testing/python/collect.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,15 @@ def test_passed(x):
634634
result = testdir.runpytest()
635635
result.stdout.fnmatch_lines('* 3 passed in *')
636636

637+
def test_function_original_name(self, testdir):
638+
items = testdir.getitems("""
639+
import pytest
640+
@pytest.mark.parametrize('arg', [1,2])
641+
def test_func(arg):
642+
pass
643+
""")
644+
assert [x.originalname for x in items] == ['test_func', 'test_func']
645+
637646

638647
class TestSorting:
639648
def test_check_equality(self, testdir):

testing/test_runner.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,3 +668,68 @@ def runtest(self):
668668
assert sys.last_type is IndexError
669669
assert sys.last_value.args[0] == 'TEST'
670670
assert sys.last_traceback
671+
672+
673+
class TestReportContents:
674+
"""
675+
Test user-level API of ``TestReport`` objects.
676+
"""
677+
678+
def getrunner(self):
679+
return lambda item: runner.runtestprotocol(item, log=False)
680+
681+
def test_longreprtext_pass(self, testdir):
682+
reports = testdir.runitem("""
683+
def test_func():
684+
pass
685+
""")
686+
rep = reports[1]
687+
assert rep.longreprtext == ''
688+
689+
def test_longreprtext_failure(self, testdir):
690+
reports = testdir.runitem("""
691+
def test_func():
692+
x = 1
693+
assert x == 4
694+
""")
695+
rep = reports[1]
696+
assert 'assert 1 == 4' in rep.longreprtext
697+
698+
def test_captured_text(self, testdir):
699+
reports = testdir.runitem("""
700+
import pytest
701+
import sys
702+
703+
@pytest.fixture
704+
def fix():
705+
sys.stdout.write('setup: stdout\\n')
706+
sys.stderr.write('setup: stderr\\n')
707+
yield
708+
sys.stdout.write('teardown: stdout\\n')
709+
sys.stderr.write('teardown: stderr\\n')
710+
assert 0
711+
712+
def test_func(fix):
713+
sys.stdout.write('call: stdout\\n')
714+
sys.stderr.write('call: stderr\\n')
715+
assert 0
716+
""")
717+
setup, call, teardown = reports
718+
assert setup.capstdout == 'setup: stdout\n'
719+
assert call.capstdout == 'setup: stdout\ncall: stdout\n'
720+
assert teardown.capstdout == 'setup: stdout\ncall: stdout\nteardown: stdout\n'
721+
722+
assert setup.capstderr == 'setup: stderr\n'
723+
assert call.capstderr == 'setup: stderr\ncall: stderr\n'
724+
assert teardown.capstderr == 'setup: stderr\ncall: stderr\nteardown: stderr\n'
725+
726+
def test_no_captured_text(self, testdir):
727+
reports = testdir.runitem("""
728+
def test_func():
729+
pass
730+
""")
731+
rep = reports[1]
732+
assert rep.capstdout == ''
733+
assert rep.capstderr == ''
734+
735+

0 commit comments

Comments
 (0)