Merge branch 'master' into features
This commit is contained in:
commit
6c170201d6
1
AUTHORS
1
AUTHORS
|
@ -33,6 +33,7 @@ Endre Galaczi
|
||||||
Elizaveta Shashkova
|
Elizaveta Shashkova
|
||||||
Eric Hunsberger
|
Eric Hunsberger
|
||||||
Eric Siegerman
|
Eric Siegerman
|
||||||
|
Erik M. Bray
|
||||||
Florian Bruhin
|
Florian Bruhin
|
||||||
Floris Bruynooghe
|
Floris Bruynooghe
|
||||||
Gabriel Reis
|
Gabriel Reis
|
||||||
|
|
14
CHANGELOG
14
CHANGELOG
|
@ -40,6 +40,20 @@ Bug Fixes
|
||||||
2.8.6.dev1
|
2.8.6.dev1
|
||||||
==========
|
==========
|
||||||
|
|
||||||
|
- fix #1259: allow for double nodeids in junitxml,
|
||||||
|
this was a regression failing plugins combinations
|
||||||
|
like pytest-pep8 + pytest-flakes
|
||||||
|
|
||||||
|
- Workaround for exception that occurs in pyreadline when using
|
||||||
|
``--pdb`` with standard I/O capture enabled.
|
||||||
|
Thanks Erik M. Bray for the PR.
|
||||||
|
|
||||||
|
- fix #900: Better error message in case the target of a ``monkeypatch`` call
|
||||||
|
raises an ``ImportError``.
|
||||||
|
|
||||||
|
- fix #1292: monkeypatch calls (setattr, setenv, etc.) are now O(1).
|
||||||
|
Thanks David R. MacIver for the report and Bruno Oliveira for the PR.
|
||||||
|
|
||||||
|
|
||||||
2.8.5
|
2.8.5
|
||||||
=====
|
=====
|
||||||
|
|
117
README.rst
117
README.rst
|
@ -1,12 +1,14 @@
|
||||||
======
|
.. image:: doc/en/img/pytest1.png
|
||||||
pytest
|
:target: http://pytest.org
|
||||||
======
|
:align: center
|
||||||
|
:alt: pytest
|
||||||
|
|
||||||
The ``pytest`` testing tool makes it easy to write small tests, yet
|
------
|
||||||
scales to support complex functional testing.
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/v/pytest.svg
|
.. image:: https://img.shields.io/pypi/v/pytest.svg
|
||||||
:target: https://pypi.python.org/pypi/pytest
|
:target: https://pypi.python.org/pypi/pytest
|
||||||
|
.. image:: https://img.shields.io/pypi/pyversions/pytest.svg
|
||||||
|
:target: https://pypi.python.org/pypi/pytest
|
||||||
.. image:: https://img.shields.io/coveralls/pytest-dev/pytest/master.svg
|
.. image:: https://img.shields.io/coveralls/pytest-dev/pytest/master.svg
|
||||||
:target: https://coveralls.io/r/pytest-dev/pytest
|
:target: https://coveralls.io/r/pytest-dev/pytest
|
||||||
.. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master
|
.. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master
|
||||||
|
@ -14,53 +16,84 @@ scales to support complex functional testing.
|
||||||
.. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true
|
.. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true
|
||||||
:target: https://ci.appveyor.com/project/pytestbot/pytest
|
:target: https://ci.appveyor.com/project/pytestbot/pytest
|
||||||
|
|
||||||
Documentation: http://pytest.org/latest/
|
The ``pytest`` framework makes it easy to write small tests, yet
|
||||||
|
scales to support complex functional testing for applications and libraries.
|
||||||
|
|
||||||
Changelog: http://pytest.org/latest/changelog.html
|
An example of a simple test:
|
||||||
|
|
||||||
Issues: https://github.com/pytest-dev/pytest/issues
|
.. code-block:: python
|
||||||
|
|
||||||
|
# content of test_sample.py
|
||||||
|
def func(x):
|
||||||
|
return x + 1
|
||||||
|
|
||||||
|
def test_answer():
|
||||||
|
assert func(3) == 5
|
||||||
|
|
||||||
|
|
||||||
|
To execute it::
|
||||||
|
|
||||||
|
$ py.test
|
||||||
|
======= test session starts ========
|
||||||
|
platform linux -- Python 3.4.3, pytest-2.8.5, py-1.4.31, pluggy-0.3.1
|
||||||
|
collected 1 items
|
||||||
|
|
||||||
|
test_sample.py F
|
||||||
|
|
||||||
|
======= FAILURES ========
|
||||||
|
_______ test_answer ________
|
||||||
|
|
||||||
|
def test_answer():
|
||||||
|
> assert func(3) == 5
|
||||||
|
E assert 4 == 5
|
||||||
|
E + where 4 = func(3)
|
||||||
|
|
||||||
|
test_sample.py:5: AssertionError
|
||||||
|
======= 1 failed in 0.12 seconds ========
|
||||||
|
|
||||||
|
Due to ``py.test``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started <http://pytest.org/latest/getting-started.html#our-first-test-run>`_ for more examples.
|
||||||
|
|
||||||
|
|
||||||
Features
|
Features
|
||||||
--------
|
--------
|
||||||
|
|
||||||
- `auto-discovery
|
- Detailed info on failing `assert statements <http://pytest.org/latest/assert.html>`_ (no need to remember ``self.assert*`` names);
|
||||||
|
|
||||||
|
- `Auto-discovery
|
||||||
<http://pytest.org/latest/goodpractises.html#python-test-discovery>`_
|
<http://pytest.org/latest/goodpractises.html#python-test-discovery>`_
|
||||||
of test modules and functions,
|
of test modules and functions;
|
||||||
- detailed info on failing `assert statements <http://pytest.org/latest/assert.html>`_ (no need to remember ``self.assert*`` names)
|
|
||||||
- `modular fixtures <http://pytest.org/latest/fixture.html>`_ for
|
- `Modular fixtures <http://pytest.org/latest/fixture.html>`_ for
|
||||||
managing small or parametrized long-lived test resources.
|
managing small or parametrized long-lived test resources;
|
||||||
- multi-paradigm support: you can use ``pytest`` to run test suites based
|
|
||||||
on `unittest <http://pytest.org/latest/unittest.html>`_ (or trial),
|
- Can run `unittest <http://pytest.org/latest/unittest.html>`_ (or trial),
|
||||||
`nose <http://pytest.org/latest/nose.html>`_
|
`nose <http://pytest.org/latest/nose.html>`_ test suites out of the box;
|
||||||
- single-source compatibility from Python2.6 all the way up to
|
|
||||||
Python3.5, PyPy-2.3, (jython-2.5 untested)
|
- Python2.6+, Python3.2+, PyPy-2.3, Jython-2.5 (untested);
|
||||||
|
|
||||||
|
- Rich plugin architecture, with over 150+ `external plugins <http://pytest.org/latest/plugins.html#installing-external-plugins-searching>`_ and thriving comminity;
|
||||||
|
|
||||||
|
|
||||||
- many `external plugins <http://pytest.org/latest/plugins.html#installing-external-plugins-searching>`_.
|
Documentation
|
||||||
|
-------------
|
||||||
|
|
||||||
A simple example for a test:
|
For full documentation, including installation, tutorials and PDF documents, please see http://pytest.org.
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
# content of test_module.py
|
|
||||||
def test_function():
|
|
||||||
i = 4
|
|
||||||
assert i == 3
|
|
||||||
|
|
||||||
which can be run with ``py.test test_module.py``. See `getting-started <http://pytest.org/latest/getting-started.html#our-first-test-run>`_ for more examples.
|
|
||||||
|
|
||||||
For much more info, including PDF docs, see
|
|
||||||
|
|
||||||
http://pytest.org
|
|
||||||
|
|
||||||
and report bugs at:
|
|
||||||
|
|
||||||
https://github.com/pytest-dev/pytest/issues
|
|
||||||
|
|
||||||
and checkout or fork repo at:
|
|
||||||
|
|
||||||
https://github.com/pytest-dev/pytest
|
|
||||||
|
|
||||||
|
|
||||||
Copyright Holger Krekel and others, 2004-2015
|
Bugs/Requests
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Please use the `GitHub issue tracker <https://github.com/pytest-dev/pytest/issues>`_ to submit bugs or request features.
|
||||||
|
|
||||||
|
|
||||||
|
Changelog
|
||||||
|
---------
|
||||||
|
|
||||||
|
Consult the `Changelog <http://pytest.org/latest/changelog.html>`_ page for fixes and enhancements of each version.
|
||||||
|
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
Copyright Holger Krekel and others, 2004-2016.
|
||||||
Licensed under the MIT license.
|
Licensed under the MIT license.
|
||||||
|
|
|
@ -31,6 +31,7 @@ def pytest_addoption(parser):
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_load_initial_conftests(early_config, parser, args):
|
def pytest_load_initial_conftests(early_config, parser, args):
|
||||||
|
_readline_workaround()
|
||||||
ns = early_config.known_args_namespace
|
ns = early_config.known_args_namespace
|
||||||
pluginmanager = early_config.pluginmanager
|
pluginmanager = early_config.pluginmanager
|
||||||
capman = CaptureManager(ns.capture)
|
capman = CaptureManager(ns.capture)
|
||||||
|
@ -442,3 +443,30 @@ class DontReadFromInput:
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _readline_workaround():
|
||||||
|
"""
|
||||||
|
Ensure readline is imported so that it attaches to the correct stdio
|
||||||
|
handles on Windows.
|
||||||
|
|
||||||
|
Pdb uses readline support where available--when not running from the Python
|
||||||
|
prompt, the readline module is not imported until running the pdb REPL. If
|
||||||
|
running py.test with the --pdb option this means the readline module is not
|
||||||
|
imported until after I/O capture has been started.
|
||||||
|
|
||||||
|
This is a problem for pyreadline, which is often used to implement readline
|
||||||
|
support on Windows, as it does not attach to the correct handles for stdout
|
||||||
|
and/or stdin if they have been redirected by the FDCapture mechanism. This
|
||||||
|
workaround ensures that readline is imported before I/O capture is setup so
|
||||||
|
that it can attach to the actual stdin/out for the console.
|
||||||
|
|
||||||
|
See https://github.com/pytest-dev/pytest/pull/1281
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not sys.platform.startswith('win32'):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
import readline # noqa
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
|
@ -282,12 +282,11 @@ def pytest_keyboard_interrupt(excinfo):
|
||||||
""" called for keyboard interrupt. """
|
""" called for keyboard interrupt. """
|
||||||
|
|
||||||
def pytest_exception_interact(node, call, report):
|
def pytest_exception_interact(node, call, report):
|
||||||
""" (experimental, new in 2.4) called when
|
"""called when an exception was raised which can potentially be
|
||||||
an exception was raised which can potentially be
|
|
||||||
interactively handled.
|
interactively handled.
|
||||||
|
|
||||||
This hook is only called if an exception was raised
|
This hook is only called if an exception was raised
|
||||||
that is not an internal exception like "skip.Exception".
|
that is not an internal exception like ``skip.Exception``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def pytest_enter_pdb(config):
|
def pytest_enter_pdb(config):
|
||||||
|
|
|
@ -71,7 +71,6 @@ class _NodeReporter(object):
|
||||||
self.testcase = None
|
self.testcase = None
|
||||||
self.attrs = {}
|
self.attrs = {}
|
||||||
|
|
||||||
|
|
||||||
def append(self, node):
|
def append(self, node):
|
||||||
self.xml.add_stats(type(node).__name__)
|
self.xml.add_stats(type(node).__name__)
|
||||||
self.nodes.append(node)
|
self.nodes.append(node)
|
||||||
|
@ -82,7 +81,6 @@ class _NodeReporter(object):
|
||||||
self.property_insert_order.append(name)
|
self.property_insert_order.append(name)
|
||||||
self.properties[name] = bin_xml_escape(value)
|
self.properties[name] = bin_xml_escape(value)
|
||||||
|
|
||||||
|
|
||||||
def make_properties_node(self):
|
def make_properties_node(self):
|
||||||
"""Return a Junit node containing custom properties, if any.
|
"""Return a Junit node containing custom properties, if any.
|
||||||
"""
|
"""
|
||||||
|
@ -93,7 +91,6 @@ class _NodeReporter(object):
|
||||||
])
|
])
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def record_testreport(self, testreport):
|
def record_testreport(self, testreport):
|
||||||
assert not self.testcase
|
assert not self.testcase
|
||||||
names = mangle_testnames(testreport.nodeid.split("::"))
|
names = mangle_testnames(testreport.nodeid.split("::"))
|
||||||
|
@ -182,7 +179,6 @@ class _NodeReporter(object):
|
||||||
message=skipreason))
|
message=skipreason))
|
||||||
self._write_captured_output(report)
|
self._write_captured_output(report)
|
||||||
|
|
||||||
|
|
||||||
def finalize(self):
|
def finalize(self):
|
||||||
data = self.to_xml().unicode(indent=0)
|
data = self.to_xml().unicode(indent=0)
|
||||||
self.__dict__.clear()
|
self.__dict__.clear()
|
||||||
|
@ -262,6 +258,14 @@ class LogXML(object):
|
||||||
self.node_reporters = {} # nodeid -> _NodeReporter
|
self.node_reporters = {} # nodeid -> _NodeReporter
|
||||||
self.node_reporters_ordered = []
|
self.node_reporters_ordered = []
|
||||||
|
|
||||||
|
def finalize(self, report):
|
||||||
|
nodeid = getattr(report, 'nodeid', report)
|
||||||
|
# local hack to handle xdist report order
|
||||||
|
slavenode = getattr(report, 'node', None)
|
||||||
|
reporter = self.node_reporters.pop((nodeid, slavenode))
|
||||||
|
if reporter is not None:
|
||||||
|
reporter.finalize()
|
||||||
|
|
||||||
def node_reporter(self, report):
|
def node_reporter(self, report):
|
||||||
nodeid = getattr(report, 'nodeid', report)
|
nodeid = getattr(report, 'nodeid', report)
|
||||||
# local hack to handle xdist report order
|
# local hack to handle xdist report order
|
||||||
|
@ -270,7 +274,7 @@ class LogXML(object):
|
||||||
key = nodeid, slavenode
|
key = nodeid, slavenode
|
||||||
|
|
||||||
if key in self.node_reporters:
|
if key in self.node_reporters:
|
||||||
#TODO: breasks for --dist=each
|
# TODO: breasks for --dist=each
|
||||||
return self.node_reporters[key]
|
return self.node_reporters[key]
|
||||||
reporter = _NodeReporter(nodeid, self)
|
reporter = _NodeReporter(nodeid, self)
|
||||||
self.node_reporters[key] = reporter
|
self.node_reporters[key] = reporter
|
||||||
|
@ -324,7 +328,7 @@ class LogXML(object):
|
||||||
reporter.append_skipped(report)
|
reporter.append_skipped(report)
|
||||||
self.update_testcase_duration(report)
|
self.update_testcase_duration(report)
|
||||||
if report.when == "teardown":
|
if report.when == "teardown":
|
||||||
self.node_reporter(report).finalize()
|
self.finalize(report)
|
||||||
|
|
||||||
def update_testcase_duration(self, report):
|
def update_testcase_duration(self, report):
|
||||||
"""accumulates total duration for nodeid from given report and updates
|
"""accumulates total duration for nodeid from given report and updates
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
""" monkeypatching and mocking functionality. """
|
""" monkeypatching and mocking functionality. """
|
||||||
|
|
||||||
import os, sys
|
import os, sys
|
||||||
|
import re
|
||||||
|
|
||||||
from py.builtin import _basestring
|
from py.builtin import _basestring
|
||||||
|
|
||||||
|
|
||||||
|
RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$")
|
||||||
|
|
||||||
|
|
||||||
def pytest_funcarg__monkeypatch(request):
|
def pytest_funcarg__monkeypatch(request):
|
||||||
"""The returned ``monkeypatch`` funcarg provides these
|
"""The returned ``monkeypatch`` funcarg provides these
|
||||||
helper methods to modify objects, dictionaries or os.environ::
|
helper methods to modify objects, dictionaries or os.environ::
|
||||||
|
@ -34,14 +40,28 @@ def derive_importpath(import_path, raising):
|
||||||
(import_path,))
|
(import_path,))
|
||||||
rest = []
|
rest = []
|
||||||
target = import_path
|
target = import_path
|
||||||
|
target_parts = set(target.split("."))
|
||||||
while target:
|
while target:
|
||||||
try:
|
try:
|
||||||
obj = __import__(target, None, None, "__doc__")
|
obj = __import__(target, None, None, "__doc__")
|
||||||
except ImportError:
|
except ImportError as ex:
|
||||||
|
if hasattr(ex, 'name'):
|
||||||
|
# Python >= 3.3
|
||||||
|
failed_name = ex.name
|
||||||
|
else:
|
||||||
|
match = RE_IMPORT_ERROR_NAME.match(ex.args[0])
|
||||||
|
assert match
|
||||||
|
failed_name = match.group(1)
|
||||||
|
|
||||||
if "." not in target:
|
if "." not in target:
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
pytest.fail("could not import any sub part: %s" %
|
pytest.fail("could not import any sub part: %s" %
|
||||||
import_path)
|
import_path)
|
||||||
|
elif failed_name != target \
|
||||||
|
and not any(p == failed_name for p in target_parts):
|
||||||
|
# target is importable but causes ImportError itself
|
||||||
|
__tracebackhide__ = True
|
||||||
|
pytest.fail("import error in %s: %s" % (target, ex.args[0]))
|
||||||
target, name = target.rsplit(".", 1)
|
target, name = target.rsplit(".", 1)
|
||||||
rest.append(name)
|
rest.append(name)
|
||||||
else:
|
else:
|
||||||
|
@ -106,7 +126,7 @@ class monkeypatch:
|
||||||
# avoid class descriptors like staticmethod/classmethod
|
# avoid class descriptors like staticmethod/classmethod
|
||||||
if inspect.isclass(target):
|
if inspect.isclass(target):
|
||||||
oldval = target.__dict__.get(name, notset)
|
oldval = target.__dict__.get(name, notset)
|
||||||
self._setattr.insert(0, (target, name, oldval))
|
self._setattr.append((target, name, oldval))
|
||||||
setattr(target, name, value)
|
setattr(target, name, value)
|
||||||
|
|
||||||
def delattr(self, target, name=notset, raising=True):
|
def delattr(self, target, name=notset, raising=True):
|
||||||
|
@ -132,13 +152,12 @@ class monkeypatch:
|
||||||
if raising:
|
if raising:
|
||||||
raise AttributeError(name)
|
raise AttributeError(name)
|
||||||
else:
|
else:
|
||||||
self._setattr.insert(0, (target, name,
|
self._setattr.append((target, name, getattr(target, name, notset)))
|
||||||
getattr(target, name, notset)))
|
|
||||||
delattr(target, name)
|
delattr(target, name)
|
||||||
|
|
||||||
def setitem(self, dic, name, value):
|
def setitem(self, dic, name, value):
|
||||||
""" Set dictionary entry ``name`` to value. """
|
""" Set dictionary entry ``name`` to value. """
|
||||||
self._setitem.insert(0, (dic, name, dic.get(name, notset)))
|
self._setitem.append((dic, name, dic.get(name, notset)))
|
||||||
dic[name] = value
|
dic[name] = value
|
||||||
|
|
||||||
def delitem(self, dic, name, raising=True):
|
def delitem(self, dic, name, raising=True):
|
||||||
|
@ -151,7 +170,7 @@ class monkeypatch:
|
||||||
if raising:
|
if raising:
|
||||||
raise KeyError(name)
|
raise KeyError(name)
|
||||||
else:
|
else:
|
||||||
self._setitem.insert(0, (dic, name, dic.get(name, notset)))
|
self._setitem.append((dic, name, dic.get(name, notset)))
|
||||||
del dic[name]
|
del dic[name]
|
||||||
|
|
||||||
def setenv(self, name, value, prepend=None):
|
def setenv(self, name, value, prepend=None):
|
||||||
|
@ -203,13 +222,13 @@ class monkeypatch:
|
||||||
calling `undo()` will undo all of the changes made in
|
calling `undo()` will undo all of the changes made in
|
||||||
both functions.
|
both functions.
|
||||||
"""
|
"""
|
||||||
for obj, name, value in self._setattr:
|
for obj, name, value in reversed(self._setattr):
|
||||||
if value is not notset:
|
if value is not notset:
|
||||||
setattr(obj, name, value)
|
setattr(obj, name, value)
|
||||||
else:
|
else:
|
||||||
delattr(obj, name)
|
delattr(obj, name)
|
||||||
self._setattr[:] = []
|
self._setattr[:] = []
|
||||||
for dictionary, name, value in self._setitem:
|
for dictionary, name, value in reversed(self._setitem):
|
||||||
if value is notset:
|
if value is notset:
|
||||||
try:
|
try:
|
||||||
del dictionary[name]
|
del dictionary[name]
|
||||||
|
|
|
@ -333,7 +333,7 @@ The result of this test will be successful::
|
||||||
Parametrizing test methods through per-class configuration
|
Parametrizing test methods through per-class configuration
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
|
|
||||||
.. _`unittest parametrizer`: http://code.google.com/p/unittest-ext/source/browse/trunk/params.py
|
.. _`unittest parametrizer`: https://github.com/testing-cabal/unittest-ext/blob/master/params.py
|
||||||
|
|
||||||
|
|
||||||
Here is an example ``pytest_generate_function`` function implementing a
|
Here is an example ``pytest_generate_function`` function implementing a
|
||||||
|
|
|
@ -120,7 +120,7 @@ in a managed class/module/function scope.
|
||||||
|
|
||||||
.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration
|
.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration
|
||||||
|
|
||||||
Can I yield multiple values from a fixture function function?
|
Can I yield multiple values from a fixture function?
|
||||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
There are two conceptual reasons why yielding from a factory function
|
There are two conceptual reasons why yielding from a factory function
|
||||||
|
|
|
@ -5,7 +5,6 @@ Getting started basics
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
index
|
|
||||||
getting-started
|
getting-started
|
||||||
usage
|
usage
|
||||||
goodpractises
|
goodpractises
|
||||||
|
|
|
@ -18,6 +18,7 @@ def runandparse(testdir, *args):
|
||||||
|
|
||||||
def assert_attr(node, **kwargs):
|
def assert_attr(node, **kwargs):
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
|
|
||||||
def nodeval(node, name):
|
def nodeval(node, name):
|
||||||
anode = node.getAttributeNode(name)
|
anode = node.getAttributeNode(name)
|
||||||
if anode is not None:
|
if anode is not None:
|
||||||
|
@ -667,10 +668,13 @@ def test_runs_twice(testdir):
|
||||||
pass
|
pass
|
||||||
''')
|
''')
|
||||||
|
|
||||||
result = testdir.runpytest(f, f, '--junitxml', testdir.tmpdir.join("test.xml"))
|
result, dom = runandparse(testdir, f, f)
|
||||||
assert 'INTERNALERROR' not in str(result.stdout)
|
assert 'INTERNALERROR' not in result.stdout.str()
|
||||||
|
first, second = [x['classname'] for x in dom.find_by_tag("testcase")]
|
||||||
|
assert first == second
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail(reason='hangs', run=False)
|
||||||
def test_runs_twice_xdist(testdir):
|
def test_runs_twice_xdist(testdir):
|
||||||
pytest.importorskip('xdist')
|
pytest.importorskip('xdist')
|
||||||
f = testdir.makepyfile('''
|
f = testdir.makepyfile('''
|
||||||
|
@ -678,7 +682,60 @@ def test_runs_twice_xdist(testdir):
|
||||||
pass
|
pass
|
||||||
''')
|
''')
|
||||||
|
|
||||||
result = testdir.runpytest(f,
|
result, dom = runandparse(
|
||||||
'--dist', 'each', '--tx', '2*popen',
|
testdir, f,
|
||||||
'--junitxml', testdir.tmpdir.join("test.xml"))
|
'--dist', 'each', '--tx', '2*popen',)
|
||||||
assert 'INTERNALERROR' not in str(result.stdout)
|
assert 'INTERNALERROR' not in result.stdout.str()
|
||||||
|
first, second = [x['classname'] for x in dom.find_by_tag("testcase")]
|
||||||
|
assert first == second
|
||||||
|
|
||||||
|
|
||||||
|
def test_fancy_items_regression(testdir):
|
||||||
|
# issue 1259
|
||||||
|
testdir.makeconftest("""
|
||||||
|
import pytest
|
||||||
|
class FunItem(pytest.Item):
|
||||||
|
def runtest(self):
|
||||||
|
pass
|
||||||
|
class NoFunItem(pytest.Item):
|
||||||
|
def runtest(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class FunCollector(pytest.File):
|
||||||
|
def collect(self):
|
||||||
|
return [
|
||||||
|
FunItem('a', self),
|
||||||
|
NoFunItem('a', self),
|
||||||
|
NoFunItem('b', self),
|
||||||
|
]
|
||||||
|
|
||||||
|
def pytest_collect_file(path, parent):
|
||||||
|
if path.check(ext='.py'):
|
||||||
|
return FunCollector(path, parent)
|
||||||
|
""")
|
||||||
|
|
||||||
|
testdir.makepyfile('''
|
||||||
|
def test_pass():
|
||||||
|
pass
|
||||||
|
''')
|
||||||
|
|
||||||
|
result, dom = runandparse(testdir)
|
||||||
|
|
||||||
|
assert 'INTERNALERROR' not in result.stdout.str()
|
||||||
|
|
||||||
|
items = sorted(
|
||||||
|
'%(classname)s %(name)s %(file)s' % x
|
||||||
|
|
||||||
|
for x in dom.find_by_tag("testcase"))
|
||||||
|
import pprint
|
||||||
|
pprint.pprint(items)
|
||||||
|
assert items == [
|
||||||
|
u'conftest a conftest.py',
|
||||||
|
u'conftest a conftest.py',
|
||||||
|
u'conftest b conftest.py',
|
||||||
|
u'test_fancy_items_regression a test_fancy_items_regression.py',
|
||||||
|
u'test_fancy_items_regression a test_fancy_items_regression.py',
|
||||||
|
u'test_fancy_items_regression b test_fancy_items_regression.py',
|
||||||
|
u'test_fancy_items_regression test_pass'
|
||||||
|
u' test_fancy_items_regression.py',
|
||||||
|
]
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import os, sys
|
import os, sys
|
||||||
|
import textwrap
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.monkeypatch import monkeypatch as MonkeyPatch
|
from _pytest.monkeypatch import monkeypatch as MonkeyPatch
|
||||||
|
|
||||||
|
@ -245,6 +247,21 @@ def test_issue185_time_breaks(testdir):
|
||||||
*1 passed*
|
*1 passed*
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
def test_importerror(testdir):
|
||||||
|
p = testdir.mkpydir("package")
|
||||||
|
p.join("a.py").write(textwrap.dedent("""\
|
||||||
|
import doesnotexist
|
||||||
|
|
||||||
|
x = 1
|
||||||
|
"""))
|
||||||
|
testdir.tmpdir.join("test_importerror.py").write(textwrap.dedent("""\
|
||||||
|
def test_importerror(monkeypatch):
|
||||||
|
monkeypatch.setattr('package.a.x', 2)
|
||||||
|
"""))
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines("""
|
||||||
|
*import error in package.a.x: No module named {0}doesnotexist{0}*
|
||||||
|
""".format("'" if sys.version_info > (3, 0) else ""))
|
||||||
|
|
||||||
|
|
||||||
class SampleNew(object):
|
class SampleNew(object):
|
||||||
|
|
Loading…
Reference in New Issue