Merged in hpk42/pytest-patches/testrefactor (pull request #284)

majorly refactor pytester and speed/streamline  tests
This commit is contained in:
holger krekel 2015-04-29 16:32:28 +02:00
commit d6670bd6a8
25 changed files with 368 additions and 339 deletions

View File

@ -43,6 +43,14 @@
implementations. Use the ``hookwrapper`` mechanism instead already implementations. Use the ``hookwrapper`` mechanism instead already
introduced with pytest-2.7. introduced with pytest-2.7.
- speed up pytest's own test suite considerably by using inprocess
tests by default (testrun can be modified with --runpytest=subprocess
to create subprocesses in many places instead). The main
APIs to run pytest in a test is "runpytest()" or "runpytest_subprocess"
and "runpytest_inprocess" if you need a particular way of running
the test. In all cases you get back a RunResult but the inprocess
one will also have a "reprec" attribute with the recorded events/reports.
2.7.1.dev (compared to 2.7.0) 2.7.1.dev (compared to 2.7.0)
----------------------------- -----------------------------

View File

@ -28,18 +28,25 @@ def main(args=None, plugins=None):
:arg plugins: list of plugin objects to be auto-registered during :arg plugins: list of plugin objects to be auto-registered during
initialization. initialization.
""" """
try:
try: try:
config = _prepareconfig(args, plugins) config = _prepareconfig(args, plugins)
except ConftestImportFailure: except ConftestImportFailure as e:
e = sys.exc_info()[1]
tw = py.io.TerminalWriter(sys.stderr) tw = py.io.TerminalWriter(sys.stderr)
for line in traceback.format_exception(*e.excinfo): for line in traceback.format_exception(*e.excinfo):
tw.line(line.rstrip(), red=True) tw.line(line.rstrip(), red=True)
tw.line("ERROR: could not load %s\n" % (e.path), red=True) tw.line("ERROR: could not load %s\n" % (e.path), red=True)
return 4 return 4
else: else:
try:
config.pluginmanager.check_pending() config.pluginmanager.check_pending()
return config.hook.pytest_cmdline_main(config=config) return config.hook.pytest_cmdline_main(config=config)
finally:
config._ensure_unconfigure()
except UsageError as e:
for msg in e.args:
sys.stderr.write("ERROR: %s\n" %(msg,))
return 4
class cmdline: # compatibility namespace class cmdline: # compatibility namespace
main = staticmethod(main) main = staticmethod(main)
@ -81,12 +88,18 @@ def _prepareconfig(args=None, plugins=None):
if not isinstance(args, str): if not isinstance(args, str):
raise ValueError("not a string or argument list: %r" % (args,)) raise ValueError("not a string or argument list: %r" % (args,))
args = shlex.split(args) args = shlex.split(args)
pluginmanager = get_config().pluginmanager config = get_config()
pluginmanager = config.pluginmanager
try:
if plugins: if plugins:
for plugin in plugins: for plugin in plugins:
pluginmanager.register(plugin) pluginmanager.register(plugin)
return pluginmanager.hook.pytest_cmdline_parse( return pluginmanager.hook.pytest_cmdline_parse(
pluginmanager=pluginmanager, args=args) pluginmanager=pluginmanager, args=args)
except BaseException:
config._ensure_unconfigure()
raise
def exclude_pytest_names(name): def exclude_pytest_names(name):
return not name.startswith(name) or name == "pytest_plugins" or \ return not name.startswith(name) or name == "pytest_plugins" or \
@ -259,7 +272,10 @@ class PytestPluginManager(PluginManager):
def consider_pluginarg(self, arg): def consider_pluginarg(self, arg):
if arg.startswith("no:"): if arg.startswith("no:"):
self.set_blocked(arg[3:]) name = arg[3:]
self.set_blocked(name)
if not name.startswith("pytest_"):
self.set_blocked("pytest_" + name)
else: else:
self.import_plugin(arg) self.import_plugin(arg)

View File

@ -83,10 +83,7 @@ def wrap_session(config, doit):
initstate = 2 initstate = 2
doit(config, session) doit(config, session)
except pytest.UsageError: except pytest.UsageError:
args = sys.exc_info()[1].args raise
for msg in args:
sys.stderr.write("ERROR: %s\n" %(msg,))
session.exitstatus = EXIT_USAGEERROR
except KeyboardInterrupt: except KeyboardInterrupt:
excinfo = py.code.ExceptionInfo() excinfo = py.code.ExceptionInfo()
config.hook.pytest_keyboard_interrupt(excinfo=excinfo) config.hook.pytest_keyboard_interrupt(excinfo=excinfo)

View File

@ -1,5 +1,7 @@
""" (disabled by default) support for testing pytest and pytest plugins. """ """ (disabled by default) support for testing pytest and pytest plugins. """
import gc
import sys import sys
import traceback
import os import os
import codecs import codecs
import re import re
@ -15,6 +17,136 @@ from _pytest.core import TracedHookExecution
from _pytest.main import Session, EXIT_OK from _pytest.main import Session, EXIT_OK
def pytest_addoption(parser):
# group = parser.getgroup("pytester", "pytester (self-tests) options")
parser.addoption('--lsof',
action="store_true", dest="lsof", default=False,
help=("run FD checks if lsof is available"))
parser.addoption('--runpytest', default="inprocess", dest="runpytest",
choices=("inprocess", "subprocess", ),
help=("run pytest sub runs in tests using an 'inprocess' "
"or 'subprocess' (python -m main) method"))
def pytest_configure(config):
# This might be called multiple times. Only take the first.
global _pytest_fullpath
try:
_pytest_fullpath
except NameError:
_pytest_fullpath = os.path.abspath(pytest.__file__.rstrip("oc"))
_pytest_fullpath = _pytest_fullpath.replace("$py.class", ".py")
if config.getvalue("lsof"):
checker = LsofFdLeakChecker()
if checker.matching_platform():
config.pluginmanager.register(checker)
class LsofFdLeakChecker(object):
def get_open_files(self):
out = self._exec_lsof()
open_files = self._parse_lsof_output(out)
return open_files
def _exec_lsof(self):
pid = os.getpid()
return py.process.cmdexec("lsof -Ffn0 -p %d" % pid)
def _parse_lsof_output(self, out):
def isopen(line):
return line.startswith('f') and ("deleted" not in line and
'mem' not in line and "txt" not in line and 'cwd' not in line)
open_files = []
for line in out.split("\n"):
if isopen(line):
fields = line.split('\0')
fd = fields[0][1:]
filename = fields[1][1:]
if filename.startswith('/'):
open_files.append((fd, filename))
return open_files
def matching_platform(self):
try:
py.process.cmdexec("lsof -v")
except py.process.cmdexec.Error:
return False
else:
return True
@pytest.hookimpl_opts(hookwrapper=True, tryfirst=True)
def pytest_runtest_item(self, item):
lines1 = self.get_open_files()
yield
if hasattr(sys, "pypy_version_info"):
gc.collect()
lines2 = self.get_open_files()
new_fds = set([t[0] for t in lines2]) - set([t[0] for t in lines1])
leaked_files = [t for t in lines2 if t[0] in new_fds]
if leaked_files:
error = []
error.append("***** %s FD leakage detected" % len(leaked_files))
error.extend([str(f) for f in leaked_files])
error.append("*** Before:")
error.extend([str(f) for f in lines1])
error.append("*** After:")
error.extend([str(f) for f in lines2])
error.append(error[0])
error.append("*** function %s:%s: %s " % item.location)
pytest.fail("\n".join(error), pytrace=False)
# XXX copied from execnet's conftest.py - needs to be merged
winpymap = {
'python2.7': r'C:\Python27\python.exe',
'python2.6': r'C:\Python26\python.exe',
'python3.1': r'C:\Python31\python.exe',
'python3.2': r'C:\Python32\python.exe',
'python3.3': r'C:\Python33\python.exe',
'python3.4': r'C:\Python34\python.exe',
'python3.5': r'C:\Python35\python.exe',
}
def getexecutable(name, cache={}):
try:
return cache[name]
except KeyError:
executable = py.path.local.sysfind(name)
if executable:
if name == "jython":
import subprocess
popen = subprocess.Popen([str(executable), "--version"],
universal_newlines=True, stderr=subprocess.PIPE)
out, err = popen.communicate()
if not err or "2.5" not in err:
executable = None
if "2.5.2" in err:
executable = None # http://bugs.jython.org/issue1790
cache[name] = executable
return executable
@pytest.fixture(params=['python2.6', 'python2.7', 'python3.3', "python3.4",
'pypy', 'pypy3'])
def anypython(request):
name = request.param
executable = getexecutable(name)
if executable is None:
if sys.platform == "win32":
executable = winpymap.get(name, None)
if executable:
executable = py.path.local(executable)
if executable.check():
return executable
pytest.skip("no suitable %s found" % (name,))
return executable
# used at least by pytest-xdist plugin # used at least by pytest-xdist plugin
@pytest.fixture @pytest.fixture
def _pytest(request): def _pytest(request):
@ -39,23 +171,6 @@ def get_public_names(l):
return [x for x in l if x[0] != "_"] return [x for x in l if x[0] != "_"]
def pytest_addoption(parser):
group = parser.getgroup("pylib")
group.addoption('--no-tools-on-path',
action="store_true", dest="notoolsonpath", default=False,
help=("discover tools on PATH instead of going through py.cmdline.")
)
def pytest_configure(config):
# This might be called multiple times. Only take the first.
global _pytest_fullpath
try:
_pytest_fullpath
except NameError:
_pytest_fullpath = os.path.abspath(pytest.__file__.rstrip("oc"))
_pytest_fullpath = _pytest_fullpath.replace("$py.class", ".py")
class ParsedCall: class ParsedCall:
def __init__(self, name, kwargs): def __init__(self, name, kwargs):
self.__dict__.update(kwargs) self.__dict__.update(kwargs)
@ -201,9 +316,11 @@ def pytest_funcarg__LineMatcher(request):
return LineMatcher return LineMatcher
def pytest_funcarg__testdir(request): def pytest_funcarg__testdir(request):
tmptestdir = TmpTestdir(request) tmptestdir = Testdir(request)
return tmptestdir return tmptestdir
rex_outcome = re.compile("(\d+) (\w+)") rex_outcome = re.compile("(\d+) (\w+)")
class RunResult: class RunResult:
"""The result of running a command. """The result of running a command.
@ -213,10 +330,10 @@ class RunResult:
:ret: The return value. :ret: The return value.
:outlines: List of lines captured from stdout. :outlines: List of lines captured from stdout.
:errlines: List of lines captures from stderr. :errlines: List of lines captures from stderr.
:stdout: LineMatcher of stdout, use ``stdout.str()`` to :stdout: :py:class:`LineMatcher` of stdout, use ``stdout.str()`` to
reconstruct stdout or the commonly used reconstruct stdout or the commonly used
``stdout.fnmatch_lines()`` method. ``stdout.fnmatch_lines()`` method.
:stderrr: LineMatcher of stderr. :stderrr: :py:class:`LineMatcher` of stderr.
:duration: Duration in seconds. :duration: Duration in seconds.
""" """
@ -229,6 +346,8 @@ class RunResult:
self.duration = duration self.duration = duration
def parseoutcomes(self): def parseoutcomes(self):
""" Return a dictionary of outcomestring->num from parsing
the terminal output that the test process produced."""
for line in reversed(self.outlines): for line in reversed(self.outlines):
if 'seconds' in line: if 'seconds' in line:
outcomes = rex_outcome.findall(line) outcomes = rex_outcome.findall(line)
@ -238,14 +357,17 @@ class RunResult:
d[cat] = int(num) d[cat] = int(num)
return d return d
def assertoutcome(self, passed=0, skipped=0, failed=0): def assert_outcomes(self, passed=0, skipped=0, failed=0):
""" assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run."""
d = self.parseoutcomes() d = self.parseoutcomes()
assert passed == d.get("passed", 0) assert passed == d.get("passed", 0)
assert skipped == d.get("skipped", 0) assert skipped == d.get("skipped", 0)
assert failed == d.get("failed", 0) assert failed == d.get("failed", 0)
class TmpTestdir:
class Testdir:
"""Temporary test directory with tools to test/run py.test itself. """Temporary test directory with tools to test/run py.test itself.
This is based on the ``tmpdir`` fixture but provides a number of This is based on the ``tmpdir`` fixture but provides a number of
@ -268,7 +390,6 @@ class TmpTestdir:
def __init__(self, request): def __init__(self, request):
self.request = request self.request = request
self.Config = request.config.__class__
# XXX remove duplication with tmpdir plugin # XXX remove duplication with tmpdir plugin
basetmp = request.config._tmpdirhandler.ensuretemp("testdir") basetmp = request.config._tmpdirhandler.ensuretemp("testdir")
name = request.function.__name__ name = request.function.__name__
@ -280,12 +401,18 @@ class TmpTestdir:
break break
self.tmpdir = tmpdir self.tmpdir = tmpdir
self.plugins = [] self.plugins = []
self._savesyspath = list(sys.path) self._savesyspath = (list(sys.path), list(sys.meta_path))
self._savemodulekeys = set(sys.modules)
self.chdir() # always chdir self.chdir() # always chdir
self.request.addfinalizer(self.finalize) self.request.addfinalizer(self.finalize)
method = self.request.config.getoption("--runpytest")
if method == "inprocess":
self._runpytest_method = self.runpytest_inprocess
elif method == "subprocess":
self._runpytest_method = self.runpytest_subprocess
def __repr__(self): def __repr__(self):
return "<TmpTestdir %r>" % (self.tmpdir,) return "<Testdir %r>" % (self.tmpdir,)
def finalize(self): def finalize(self):
"""Clean up global state artifacts. """Clean up global state artifacts.
@ -296,22 +423,21 @@ class TmpTestdir:
has finished. has finished.
""" """
sys.path[:] = self._savesyspath sys.path[:], sys.meta_path[:] = self._savesyspath
if hasattr(self, '_olddir'): if hasattr(self, '_olddir'):
self._olddir.chdir() self._olddir.chdir()
self.delete_loaded_modules() self.delete_loaded_modules()
def delete_loaded_modules(self): def delete_loaded_modules(self):
"""Delete modules that have been loaded from tmpdir. """Delete modules that have been loaded during a test.
This allows the interpreter to catch module changes in case This allows the interpreter to catch module changes in case
the module is re-imported. the module is re-imported.
""" """
for name, mod in list(sys.modules.items()): for name in set(sys.modules).difference(self._savemodulekeys):
if mod: # it seems zope.interfaces is keeping some state
fn = getattr(mod, '__file__', None) # (used by twisted related tests)
if fn and fn.startswith(str(self.tmpdir)): if name != "zope.interface":
del sys.modules[name] del sys.modules[name]
def make_hook_recorder(self, pluginmanager): def make_hook_recorder(self, pluginmanager):
@ -503,43 +629,19 @@ class TmpTestdir:
l = list(cmdlineargs) + [p] l = list(cmdlineargs) + [p]
return self.inline_run(*l) return self.inline_run(*l)
def inline_runsource1(self, *args):
"""Run a test module in process using ``pytest.main()``.
This behaves exactly like :py:meth:`inline_runsource` and
takes identical arguments. However the return value is a list
of the reports created by the pytest_runtest_logreport hook
during the run.
"""
args = list(args)
source = args.pop()
p = self.makepyfile(source)
l = list(args) + [p]
reprec = self.inline_run(*l)
reports = reprec.getreports("pytest_runtest_logreport")
assert len(reports) == 3, reports # setup/call/teardown
return reports[1]
def inline_genitems(self, *args): def inline_genitems(self, *args):
"""Run ``pytest.main(['--collectonly'])`` in-process. """Run ``pytest.main(['--collectonly'])`` in-process.
Retuns a tuple of the collected items and a Retuns a tuple of the collected items and a
:py:class:`HookRecorder` instance. :py:class:`HookRecorder` instance.
"""
return self.inprocess_run(list(args) + ['--collectonly'])
def inprocess_run(self, args, plugins=()):
"""Run ``pytest.main()`` in-process, return Items and a HookRecorder.
This runs the :py:func:`pytest.main` function to run all of This runs the :py:func:`pytest.main` function to run all of
py.test inside the test process itself like py.test inside the test process itself like
:py:meth:`inline_run`. However the return value is a tuple of :py:meth:`inline_run`. However the return value is a tuple of
the collection items and a :py:class:`HookRecorder` instance. the collection items and a :py:class:`HookRecorder` instance.
""" """
rec = self.inline_run(*args, plugins=plugins) rec = self.inline_run("--collect-only", *args)
items = [x.item for x in rec.getcalls("pytest_itemcollected")] items = [x.item for x in rec.getcalls("pytest_itemcollected")]
return items, rec return items, rec
@ -568,12 +670,50 @@ class TmpTestdir:
plugins = kwargs.get("plugins") or [] plugins = kwargs.get("plugins") or []
plugins.append(Collect()) plugins.append(Collect())
ret = pytest.main(list(args), plugins=plugins) ret = pytest.main(list(args), plugins=plugins)
assert len(rec) == 1
reprec = rec[0]
reprec.ret = ret
self.delete_loaded_modules() self.delete_loaded_modules()
if len(rec) == 1:
reprec = rec.pop()
else:
class reprec:
pass
reprec.ret = ret
return reprec return reprec
def runpytest_inprocess(self, *args, **kwargs):
""" Return result of running pytest in-process, providing a similar
interface to what self.runpytest() provides. """
if kwargs.get("syspathinsert"):
self.syspathinsert()
now = time.time()
capture = py.io.StdCapture()
try:
try:
reprec = self.inline_run(*args)
except SystemExit as e:
class reprec:
ret = e.args[0]
except Exception:
traceback.print_exc()
class reprec:
ret = 3
finally:
out, err = capture.reset()
sys.stdout.write(out)
sys.stderr.write(err)
res = RunResult(reprec.ret,
out.split("\n"), err.split("\n"),
time.time()-now)
res.reprec = reprec
return res
def runpytest(self, *args, **kwargs):
""" Run pytest inline or in a subprocess, depending on the command line
option "--runpytest" and return a :py:class:`RunResult`.
"""
return self._runpytest_method(*args, **kwargs)
def parseconfig(self, *args): def parseconfig(self, *args):
"""Return a new py.test Config instance from given commandline args. """Return a new py.test Config instance from given commandline args.
@ -745,57 +885,23 @@ class TmpTestdir:
except UnicodeEncodeError: except UnicodeEncodeError:
print("couldn't print to %s because of encoding" % (fp,)) print("couldn't print to %s because of encoding" % (fp,))
def runpybin(self, scriptname, *args): def _getpytestargs(self):
"""Run a py.* tool with arguments.
This can realy only be used to run py.test, you probably want
:py:meth:`runpytest` instead.
Returns a :py:class:`RunResult`.
"""
fullargs = self._getpybinargs(scriptname) + args
return self.run(*fullargs)
def _getpybinargs(self, scriptname):
if not self.request.config.getvalue("notoolsonpath"):
# XXX we rely on script referring to the correct environment
# we cannot use "(sys.executable,script)" # we cannot use "(sys.executable,script)"
# because on windows the script is e.g. a py.test.exe # because on windows the script is e.g. a py.test.exe
return (sys.executable, _pytest_fullpath,) # noqa return (sys.executable, _pytest_fullpath,) # noqa
else:
pytest.skip("cannot run %r with --no-tools-on-path" % scriptname)
def runpython(self, script, prepend=True): def runpython(self, script):
"""Run a python script. """Run a python script using sys.executable as interpreter.
If ``prepend`` is True then the directory from which the py
package has been imported will be prepended to sys.path.
Returns a :py:class:`RunResult`. Returns a :py:class:`RunResult`.
""" """
# XXX The prepend feature is probably not very useful since the
# split of py and pytest.
if prepend:
s = self._getsysprepend()
if s:
script.write(s + "\n" + script.read())
return self.run(sys.executable, script) return self.run(sys.executable, script)
def _getsysprepend(self):
if self.request.config.getvalue("notoolsonpath"):
s = "import sys;sys.path.insert(0,%r);" % str(py._pydir.dirpath())
else:
s = ""
return s
def runpython_c(self, command): def runpython_c(self, command):
"""Run python -c "command", return a :py:class:`RunResult`.""" """Run python -c "command", return a :py:class:`RunResult`."""
command = self._getsysprepend() + command
return self.run(sys.executable, "-c", command) return self.run(sys.executable, "-c", command)
def runpytest(self, *args): def runpytest_subprocess(self, *args, **kwargs):
"""Run py.test as a subprocess with given arguments. """Run py.test as a subprocess with given arguments.
Any plugins added to the :py:attr:`plugins` list will added Any plugins added to the :py:attr:`plugins` list will added
@ -820,7 +926,8 @@ class TmpTestdir:
plugins = [x for x in self.plugins if isinstance(x, str)] plugins = [x for x in self.plugins if isinstance(x, str)]
if plugins: if plugins:
args = ('-p', plugins[0]) + args args = ('-p', plugins[0]) + args
return self.runpybin("py.test", *args) args = self._getpytestargs() + args
return self.run(*args)
def spawn_pytest(self, string, expect_timeout=10.0): def spawn_pytest(self, string, expect_timeout=10.0):
"""Run py.test using pexpect. """Run py.test using pexpect.
@ -831,10 +938,8 @@ class TmpTestdir:
The pexpect child is returned. The pexpect child is returned.
""" """
if self.request.config.getvalue("notoolsonpath"):
pytest.skip("--no-tools-on-path prevents running pexpect-spawn tests")
basetemp = self.tmpdir.mkdir("pexpect") basetemp = self.tmpdir.mkdir("pexpect")
invoke = " ".join(map(str, self._getpybinargs("py.test"))) invoke = " ".join(map(str, self._getpytestargs()))
cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string) cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
return self.spawn(cmd, expect_timeout=expect_timeout) return self.spawn(cmd, expect_timeout=expect_timeout)

View File

@ -7,7 +7,7 @@ def test_failure_demo_fails_properly(testdir):
target = testdir.tmpdir.join(failure_demo.basename) target = testdir.tmpdir.join(failure_demo.basename)
failure_demo.copy(target) failure_demo.copy(target)
failure_demo.copy(testdir.tmpdir.join(failure_demo.basename)) failure_demo.copy(testdir.tmpdir.join(failure_demo.basename))
result = testdir.runpytest(target) result = testdir.runpytest(target, syspathinsert=True)
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*42 failed*" "*42 failed*"
]) ])

View File

@ -186,12 +186,44 @@ the plugin manager like this:
If you want to look at the names of existing plugins, use If you want to look at the names of existing plugins, use
the ``--traceconfig`` option. the ``--traceconfig`` option.
Testing plugins
---------------
pytest comes with some facilities that you can enable for testing your
plugin. Given that you have an installed plugin you can enable the
:py:class:`testdir <_pytest.pytester.Testdir>` fixture via specifying a
command line option to include the pytester plugin (``-p pytester``) or
by putting ``pytest_plugins = pytester`` into your test or
``conftest.py`` file. You then will have a ``testdir`` fixure which you
can use like this::
# content of test_myplugin.py
pytest_plugins = pytester # to get testdir fixture
def test_myplugin(testdir):
testdir.makepyfile("""
def test_example():
pass
""")
result = testdir.runpytest("--verbose")
result.fnmatch_lines("""
test_example*
""")
Note that by default ``testdir.runpytest()`` will perform a pytest
in-process. You can pass the command line option ``--runpytest=subprocess``
to have it happen in a subprocess.
Also see the :py:class:`RunResult <_pytest.pytester.RunResult>` for more
methods of the result object that you get from a call to ``runpytest``.
.. _`writinghooks`: .. _`writinghooks`:
Writing hook functions Writing hook functions
====================== ======================
.. _validation: .. _validation:
hook function validation and execution hook function validation and execution
@ -493,3 +525,13 @@ Reference of objects involved in hooks
.. autoclass:: _pytest.core.CallOutcome() .. autoclass:: _pytest.core.CallOutcome()
:members: :members:
.. currentmodule:: _pytest.pytester
.. autoclass:: Testdir()
:members: runpytest,runpytest_subprocess,runpytest_inprocess,makeconftest,makepyfile
.. autoclass:: RunResult()
:members:
.. autoclass:: LineMatcher()
:members:

View File

@ -82,7 +82,7 @@ class TestGeneralUsage:
def test_option(pytestconfig): def test_option(pytestconfig):
assert pytestconfig.option.xyz == "123" assert pytestconfig.option.xyz == "123"
""") """)
result = testdir.runpytest("-p", "pytest_xyz", "--xyz=123") result = testdir.runpytest("-p", "pytest_xyz", "--xyz=123", syspathinsert=True)
assert result.ret == 0 assert result.ret == 0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
'*1 passed*', '*1 passed*',
@ -203,7 +203,7 @@ class TestGeneralUsage:
os.chdir(os.path.dirname(os.getcwd())) os.chdir(os.path.dirname(os.getcwd()))
print (py.log) print (py.log)
""")) """))
result = testdir.runpython(p, prepend=False) result = testdir.runpython(p)
assert not result.ret assert not result.ret
def test_issue109_sibling_conftests_not_loaded(self, testdir): def test_issue109_sibling_conftests_not_loaded(self, testdir):
@ -353,7 +353,8 @@ class TestGeneralUsage:
*unrecognized* *unrecognized*
""") """)
def test_getsourcelines_error_issue553(self, testdir): def test_getsourcelines_error_issue553(self, testdir, monkeypatch):
monkeypatch.setattr("inspect.getsourcelines", None)
p = testdir.makepyfile(""" p = testdir.makepyfile("""
def raise_error(obj): def raise_error(obj):
raise IOError('source code not available') raise IOError('source code not available')

View File

@ -1,118 +0,0 @@
import pytest
import sys
pytest_plugins = "pytester",
import os, py
class LsofFdLeakChecker(object):
def get_open_files(self):
out = self._exec_lsof()
open_files = self._parse_lsof_output(out)
return open_files
def _exec_lsof(self):
pid = os.getpid()
return py.process.cmdexec("lsof -Ffn0 -p %d" % pid)
def _parse_lsof_output(self, out):
def isopen(line):
return line.startswith('f') and (
"deleted" not in line and 'mem' not in line and "txt" not in line and 'cwd' not in line)
open_files = []
for line in out.split("\n"):
if isopen(line):
fields = line.split('\0')
fd = fields[0][1:]
filename = fields[1][1:]
if filename.startswith('/'):
open_files.append((fd, filename))
return open_files
def pytest_addoption(parser):
parser.addoption('--lsof',
action="store_true", dest="lsof", default=False,
help=("run FD checks if lsof is available"))
def pytest_runtest_setup(item):
config = item.config
config._basedir = py.path.local()
if config.getvalue("lsof"):
try:
config._fd_leak_checker = LsofFdLeakChecker()
config._openfiles = config._fd_leak_checker.get_open_files()
except py.process.cmdexec.Error:
pass
#def pytest_report_header():
# return "pid: %s" % os.getpid()
def check_open_files(config):
lines2 = config._fd_leak_checker.get_open_files()
new_fds = set([t[0] for t in lines2]) - set([t[0] for t in config._openfiles])
open_files = [t for t in lines2 if t[0] in new_fds]
if open_files:
error = []
error.append("***** %s FD leakage detected" % len(open_files))
error.extend([str(f) for f in open_files])
error.append("*** Before:")
error.extend([str(f) for f in config._openfiles])
error.append("*** After:")
error.extend([str(f) for f in lines2])
error.append(error[0])
raise AssertionError("\n".join(error))
@pytest.hookimpl_opts(hookwrapper=True, trylast=True)
def pytest_runtest_teardown(item):
yield
item.config._basedir.chdir()
if hasattr(item.config, '_openfiles'):
check_open_files(item.config)
# XXX copied from execnet's conftest.py - needs to be merged
winpymap = {
'python2.7': r'C:\Python27\python.exe',
'python2.6': r'C:\Python26\python.exe',
'python3.1': r'C:\Python31\python.exe',
'python3.2': r'C:\Python32\python.exe',
'python3.3': r'C:\Python33\python.exe',
'python3.4': r'C:\Python34\python.exe',
'python3.5': r'C:\Python35\python.exe',
}
def getexecutable(name, cache={}):
try:
return cache[name]
except KeyError:
executable = py.path.local.sysfind(name)
if executable:
if name == "jython":
import subprocess
popen = subprocess.Popen([str(executable), "--version"],
universal_newlines=True, stderr=subprocess.PIPE)
out, err = popen.communicate()
if not err or "2.5" not in err:
executable = None
if "2.5.2" in err:
executable = None # http://bugs.jython.org/issue1790
cache[name] = executable
return executable
@pytest.fixture(params=['python2.6', 'python2.7', 'python3.3', "python3.4",
'pypy', 'pypy3'])
def anypython(request):
name = request.param
executable = getexecutable(name)
if executable is None:
if sys.platform == "win32":
executable = winpymap.get(name, None)
if executable:
executable = py.path.local(executable)
if executable.check():
return executable
pytest.skip("no suitable %s found" % (name,))
return executable

View File

@ -627,9 +627,7 @@ def test_setup_only_available_in_subdir(testdir):
sub1.join("test_in_sub1.py").write("def test_1(): pass") sub1.join("test_in_sub1.py").write("def test_1(): pass")
sub2.join("test_in_sub2.py").write("def test_2(): pass") sub2.join("test_in_sub2.py").write("def test_2(): pass")
result = testdir.runpytest("-v", "-s") result = testdir.runpytest("-v", "-s")
result.stdout.fnmatch_lines([ result.assert_outcomes(passed=2)
"*2 passed*"
])
def test_modulecol_roundtrip(testdir): def test_modulecol_roundtrip(testdir):
modcol = testdir.getmodulecol("pass", withinit=True) modcol = testdir.getmodulecol("pass", withinit=True)

View File

@ -100,9 +100,7 @@ class TestFillFixtures:
sub1.join("test_in_sub1.py").write("def test_1(arg1): pass") sub1.join("test_in_sub1.py").write("def test_1(arg1): pass")
sub2.join("test_in_sub2.py").write("def test_2(arg2): pass") sub2.join("test_in_sub2.py").write("def test_2(arg2): pass")
result = testdir.runpytest("-v") result = testdir.runpytest("-v")
result.stdout.fnmatch_lines([ result.assert_outcomes(passed=2)
"*2 passed*"
])
def test_extend_fixture_module_class(self, testdir): def test_extend_fixture_module_class(self, testdir):
testfile = testdir.makepyfile(""" testfile = testdir.makepyfile("""

View File

@ -292,9 +292,7 @@ class TestMetafunc:
""") """)
result = testdir.runpytest() result = testdir.runpytest()
assert result.ret == 1 assert result.ret == 1
result.stdout.fnmatch_lines([ result.assert_outcomes(failed=6)
"*6 fail*",
])
def test_parametrize_CSV(self, testdir): def test_parametrize_CSV(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
@ -375,7 +373,7 @@ class TestMetafuncFunctional:
assert metafunc.cls == TestClass assert metafunc.cls == TestClass
""") """)
result = testdir.runpytest(p, "-v") result = testdir.runpytest(p, "-v")
result.assertoutcome(passed=2) result.assert_outcomes(passed=2)
def test_addcall_with_two_funcargs_generators(self, testdir): def test_addcall_with_two_funcargs_generators(self, testdir):
testdir.makeconftest(""" testdir.makeconftest("""
@ -430,9 +428,7 @@ class TestMetafuncFunctional:
pass pass
""") """)
result = testdir.runpytest(p) result = testdir.runpytest(p)
result.stdout.fnmatch_lines([ result.assert_outcomes(passed=1)
"*1 pass*",
])
def test_generate_plugin_and_module(self, testdir): def test_generate_plugin_and_module(self, testdir):
@ -506,9 +502,7 @@ class TestMetafuncFunctional:
self.val = 1 self.val = 1
""") """)
result = testdir.runpytest(p) result = testdir.runpytest(p)
result.stdout.fnmatch_lines([ result.assert_outcomes(passed=1)
"*1 pass*",
])
def test_parametrize_functional2(self, testdir): def test_parametrize_functional2(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
@ -653,8 +647,8 @@ class TestMetafuncFunctional:
def test_function(): def test_function():
pass pass
""") """)
reprec = testdir.inline_run() reprec = testdir.runpytest()
reprec.assertoutcome(passed=1) reprec.assert_outcomes(passed=1)
def test_generate_tests_only_done_in_subdir(self, testdir): def test_generate_tests_only_done_in_subdir(self, testdir):
sub1 = testdir.mkpydir("sub1") sub1 = testdir.mkpydir("sub1")
@ -670,9 +664,7 @@ class TestMetafuncFunctional:
sub1.join("test_in_sub1.py").write("def test_1(): pass") sub1.join("test_in_sub1.py").write("def test_1(): pass")
sub2.join("test_in_sub2.py").write("def test_2(): pass") sub2.join("test_in_sub2.py").write("def test_2(): pass")
result = testdir.runpytest("-v", "-s", sub1, sub2, sub1) result = testdir.runpytest("-v", "-s", sub1, sub2, sub1)
result.stdout.fnmatch_lines([ result.assert_outcomes(passed=3)
"*3 passed*"
])
def test_generate_same_function_names_issue403(self, testdir): def test_generate_same_function_names_issue403(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
@ -687,8 +679,8 @@ class TestMetafuncFunctional:
test_x = make_tests() test_x = make_tests()
test_y = make_tests() test_y = make_tests()
""") """)
reprec = testdir.inline_run() reprec = testdir.runpytest()
reprec.assertoutcome(passed=4) reprec.assert_outcomes(passed=4)
@pytest.mark.issue463 @pytest.mark.issue463
def test_parameterize_misspelling(self, testdir): def test_parameterize_misspelling(self, testdir):

View File

@ -461,7 +461,7 @@ def test_assertion_options(testdir):
("--assert=plain", "--nomagic"), ("--assert=plain", "--nomagic"),
("--assert=plain", "--no-assert", "--nomagic")) ("--assert=plain", "--no-assert", "--nomagic"))
for opt in off_options: for opt in off_options:
result = testdir.runpytest(*opt) result = testdir.runpytest_subprocess(*opt)
assert "3 == 4" not in result.stdout.str() assert "3 == 4" not in result.stdout.str()
def test_old_assert_mode(testdir): def test_old_assert_mode(testdir):
@ -469,7 +469,7 @@ def test_old_assert_mode(testdir):
def test_in_old_mode(): def test_in_old_mode():
assert "@py_builtins" not in globals() assert "@py_builtins" not in globals()
""") """)
result = testdir.runpytest("--assert=reinterp") result = testdir.runpytest_subprocess("--assert=reinterp")
assert result.ret == 0 assert result.ret == 0
def test_triple_quoted_string_issue113(testdir): def test_triple_quoted_string_issue113(testdir):

View File

@ -453,7 +453,7 @@ def test_rewritten():
assert not os.path.exists(__cached__) assert not os.path.exists(__cached__)
assert not os.path.exists(os.path.dirname(__cached__))""") assert not os.path.exists(os.path.dirname(__cached__))""")
monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", "1") monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", "1")
assert testdir.runpytest().ret == 0 assert testdir.runpytest_subprocess().ret == 0
@pytest.mark.skipif('"__pypy__" in sys.modules') @pytest.mark.skipif('"__pypy__" in sys.modules')
def test_pyc_vs_pyo(self, testdir, monkeypatch): def test_pyc_vs_pyo(self, testdir, monkeypatch):
@ -468,12 +468,12 @@ def test_rewritten():
tmp = "--basetemp=%s" % p tmp = "--basetemp=%s" % p
monkeypatch.setenv("PYTHONOPTIMIZE", "2") monkeypatch.setenv("PYTHONOPTIMIZE", "2")
monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False) monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
assert testdir.runpybin("py.test", tmp).ret == 0 assert testdir.runpytest_subprocess(tmp).ret == 0
tagged = "test_pyc_vs_pyo." + PYTEST_TAG tagged = "test_pyc_vs_pyo." + PYTEST_TAG
assert tagged + ".pyo" in os.listdir("__pycache__") assert tagged + ".pyo" in os.listdir("__pycache__")
monkeypatch.undo() monkeypatch.undo()
monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False) monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
assert testdir.runpybin("py.test", tmp).ret == 1 assert testdir.runpytest_subprocess(tmp).ret == 1
assert tagged + ".pyc" in os.listdir("__pycache__") assert tagged + ".pyc" in os.listdir("__pycache__")
def test_package(self, testdir): def test_package(self, testdir):
@ -615,10 +615,8 @@ class TestAssertionRewriteHookDetails(object):
testdir.makepyfile(**contents) testdir.makepyfile(**contents)
testdir.maketxtfile(**{'testpkg/resource': "Load me please."}) testdir.maketxtfile(**{'testpkg/resource': "Load me please."})
result = testdir.runpytest() result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines([ result.assert_outcomes(passed=1)
'* 1 passed*',
])
def test_read_pyc(self, tmpdir): def test_read_pyc(self, tmpdir):
""" """

View File

@ -282,7 +282,7 @@ class TestLoggingInteraction:
logging.basicConfig(stream=stream) logging.basicConfig(stream=stream)
stream.close() # to free memory/release resources stream.close() # to free memory/release resources
""") """)
result = testdir.runpytest(p) result = testdir.runpytest_subprocess(p)
result.stderr.str().find("atexit") == -1 result.stderr.str().find("atexit") == -1
def test_logging_and_immediate_setupteardown(self, testdir): def test_logging_and_immediate_setupteardown(self, testdir):
@ -301,7 +301,7 @@ class TestLoggingInteraction:
""") """)
for optargs in (('--capture=sys',), ('--capture=fd',)): for optargs in (('--capture=sys',), ('--capture=fd',)):
print (optargs) print (optargs)
result = testdir.runpytest(p, *optargs) result = testdir.runpytest_subprocess(p, *optargs)
s = result.stdout.str() s = result.stdout.str()
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*WARN*hello3", # errors show first! "*WARN*hello3", # errors show first!
@ -327,7 +327,7 @@ class TestLoggingInteraction:
""") """)
for optargs in (('--capture=sys',), ('--capture=fd',)): for optargs in (('--capture=sys',), ('--capture=fd',)):
print (optargs) print (optargs)
result = testdir.runpytest(p, *optargs) result = testdir.runpytest_subprocess(p, *optargs)
s = result.stdout.str() s = result.stdout.str()
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*WARN*hello3", # errors come first "*WARN*hello3", # errors come first
@ -348,7 +348,7 @@ class TestLoggingInteraction:
logging.warn("hello432") logging.warn("hello432")
assert 0 assert 0
""") """)
result = testdir.runpytest( result = testdir.runpytest_subprocess(
p, "--traceconfig", p, "--traceconfig",
"-p", "no:capturelog") "-p", "no:capturelog")
assert result.ret != 0 assert result.ret != 0
@ -364,7 +364,7 @@ class TestLoggingInteraction:
logging.warn("hello435") logging.warn("hello435")
""") """)
# make sure that logging is still captured in tests # make sure that logging is still captured in tests
result = testdir.runpytest("-s", "-p", "no:capturelog") result = testdir.runpytest_subprocess("-s", "-p", "no:capturelog")
assert result.ret == 0 assert result.ret == 0
result.stderr.fnmatch_lines([ result.stderr.fnmatch_lines([
"WARNING*hello435*", "WARNING*hello435*",
@ -383,7 +383,7 @@ class TestLoggingInteraction:
logging.warn("hello433") logging.warn("hello433")
assert 0 assert 0
""") """)
result = testdir.runpytest(p, "-p", "no:capturelog") result = testdir.runpytest_subprocess(p, "-p", "no:capturelog")
assert result.ret != 0 assert result.ret != 0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"WARNING*hello433*", "WARNING*hello433*",
@ -461,7 +461,7 @@ class TestCaptureFixture:
os.write(1, str(42).encode('ascii')) os.write(1, str(42).encode('ascii'))
raise KeyboardInterrupt() raise KeyboardInterrupt()
""") """)
result = testdir.runpytest(p) result = testdir.runpytest_subprocess(p)
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*KeyboardInterrupt*" "*KeyboardInterrupt*"
]) ])
@ -474,7 +474,7 @@ class TestCaptureFixture:
def test_log(capsys): def test_log(capsys):
logging.error('x') logging.error('x')
""") """)
result = testdir.runpytest(p) result = testdir.runpytest_subprocess(p)
assert 'closed' not in result.stderr.str() assert 'closed' not in result.stderr.str()
@ -500,7 +500,7 @@ def test_fdfuncarg_skips_on_no_osdup(testdir):
def test_hello(capfd): def test_hello(capfd):
pass pass
""") """)
result = testdir.runpytest("--capture=no") result = testdir.runpytest_subprocess("--capture=no")
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*1 skipped*" "*1 skipped*"
]) ])
@ -563,9 +563,7 @@ def test_capture_binary_output(testdir):
test_foo() test_foo()
""") """)
result = testdir.runpytest('--assert=plain') result = testdir.runpytest('--assert=plain')
result.stdout.fnmatch_lines([ result.assert_outcomes(passed=2)
'*2 passed*',
])
class TestTextIO: class TestTextIO:
@ -885,7 +883,7 @@ class TestStdCaptureFD(TestStdCapture):
os.write(1, "hello\\n".encode("ascii")) os.write(1, "hello\\n".encode("ascii"))
assert 0 assert 0
""") """)
result = testdir.runpytest() result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines(""" result.stdout.fnmatch_lines("""
*test_x* *test_x*
*assert 0* *assert 0*
@ -936,7 +934,7 @@ class TestStdCaptureFDinvalidFD:
cap = StdCaptureFD(out=False, err=False, in_=True) cap = StdCaptureFD(out=False, err=False, in_=True)
cap.stop_capturing() cap.stop_capturing()
""") """)
result = testdir.runpytest("--capture=fd") result = testdir.runpytest_subprocess("--capture=fd")
assert result.ret == 0 assert result.ret == 0
assert result.parseoutcomes()['passed'] == 3 assert result.parseoutcomes()['passed'] == 3
@ -971,7 +969,7 @@ def test_close_and_capture_again(testdir):
os.write(1, b"hello\\n") os.write(1, b"hello\\n")
assert 0 assert 0
""") """)
result = testdir.runpytest() result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines(""" result.stdout.fnmatch_lines("""
*test_capture_again* *test_capture_again*
*assert 0* *assert 0*

View File

@ -296,7 +296,6 @@ class TestSession:
subdir.ensure("__init__.py") subdir.ensure("__init__.py")
target = subdir.join(p.basename) target = subdir.join(p.basename)
p.move(target) p.move(target)
testdir.chdir()
subdir.chdir() subdir.chdir()
config = testdir.parseconfig(p.basename) config = testdir.parseconfig(p.basename)
rcol = Session(config=config) rcol = Session(config=config)
@ -313,7 +312,7 @@ class TestSession:
def test_collect_topdir(self, testdir): def test_collect_topdir(self, testdir):
p = testdir.makepyfile("def test_func(): pass") p = testdir.makepyfile("def test_func(): pass")
id = "::".join([p.basename, "test_func"]) id = "::".join([p.basename, "test_func"])
# XXX migrate to inline_genitems? (see below) # XXX migrate to collectonly? (see below)
config = testdir.parseconfig(id) config = testdir.parseconfig(id)
topdir = testdir.tmpdir topdir = testdir.tmpdir
rcol = Session(config) rcol = Session(config)
@ -470,7 +469,6 @@ class Test_getinitialnodes:
assert col.config is config assert col.config is config
def test_pkgfile(self, testdir): def test_pkgfile(self, testdir):
testdir.chdir()
tmpdir = testdir.tmpdir tmpdir = testdir.tmpdir
subdir = tmpdir.join("subdir") subdir = tmpdir.join("subdir")
x = subdir.ensure("x.py") x = subdir.ensure("x.py")

View File

@ -75,7 +75,7 @@ class TestParseIni:
[pytest] [pytest]
addopts = --qwe addopts = --qwe
""") """)
result = testdir.runpytest("--confcutdir=.") result = testdir.inline_run("--confcutdir=.")
assert result.ret == 0 assert result.ret == 0
class TestConfigCmdlineParsing: class TestConfigCmdlineParsing:

View File

@ -961,7 +961,7 @@ class TestPytestPluginManager:
""") """)
p.copy(p.dirpath("skipping2.py")) p.copy(p.dirpath("skipping2.py"))
monkeypatch.setenv("PYTEST_PLUGINS", "skipping2") monkeypatch.setenv("PYTEST_PLUGINS", "skipping2")
result = testdir.runpytest("-rw", "-p", "skipping1", "--traceconfig") result = testdir.runpytest("-rw", "-p", "skipping1", syspathinsert=True)
assert result.ret == 0 assert result.ret == 0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"WI1*skipped plugin*skipping1*hello*", "WI1*skipped plugin*skipping1*hello*",
@ -990,7 +990,7 @@ class TestPytestPluginManager:
assert plugin is not None assert plugin is not None
""") """)
monkeypatch.setenv('PYTEST_PLUGINS', 'pytest_x500', prepend=",") monkeypatch.setenv('PYTEST_PLUGINS', 'pytest_x500', prepend=",")
result = testdir.runpytest(p) result = testdir.runpytest(p, syspathinsert=True)
assert result.ret == 0 assert result.ret == 0
result.stdout.fnmatch_lines(["*1 passed*"]) result.stdout.fnmatch_lines(["*1 passed*"])

View File

@ -1,5 +1,5 @@
from _pytest.doctest import DoctestItem, DoctestModule, DoctestTextfile from _pytest.doctest import DoctestItem, DoctestModule, DoctestTextfile
import py, pytest import py
class TestDoctests: class TestDoctests:
@ -75,8 +75,6 @@ class TestDoctests:
assert isinstance(items[0].parent, DoctestModule) assert isinstance(items[0].parent, DoctestModule)
assert items[0].parent is items[1].parent assert items[0].parent is items[1].parent
@pytest.mark.xfail('hasattr(sys, "pypy_version_info")', reason=
"pypy leaks one FD")
def test_simple_doctestfile(self, testdir): def test_simple_doctestfile(self, testdir):
p = testdir.maketxtfile(test_doc=""" p = testdir.maketxtfile(test_doc="""
>>> x = 1 >>> x = 1

View File

@ -16,7 +16,6 @@ class Standalone:
assert self.script.check() assert self.script.check()
def run(self, anypython, testdir, *args): def run(self, anypython, testdir, *args):
testdir.chdir()
return testdir._run(anypython, self.script, *args) return testdir._run(anypython, self.script, *args)
def test_gen(testdir, anypython, standalone): def test_gen(testdir, anypython, standalone):

View File

@ -53,14 +53,14 @@ def test_traceconfig(testdir):
]) ])
def test_debug(testdir, monkeypatch): def test_debug(testdir, monkeypatch):
result = testdir.runpytest("--debug") result = testdir.runpytest_subprocess("--debug")
assert result.ret == 0 assert result.ret == 0
p = testdir.tmpdir.join("pytestdebug.log") p = testdir.tmpdir.join("pytestdebug.log")
assert "pytest_sessionstart" in p.read() assert "pytest_sessionstart" in p.read()
def test_PYTEST_DEBUG(testdir, monkeypatch): def test_PYTEST_DEBUG(testdir, monkeypatch):
monkeypatch.setenv("PYTEST_DEBUG", "1") monkeypatch.setenv("PYTEST_DEBUG", "1")
result = testdir.runpytest() result = testdir.runpytest_subprocess()
assert result.ret == 0 assert result.ret == 0
result.stderr.fnmatch_lines([ result.stderr.fnmatch_lines([
"*pytest_plugin_registered*", "*pytest_plugin_registered*",

View File

@ -19,9 +19,7 @@ def test_nose_setup(testdir):
test_hello.teardown = lambda: l.append(2) test_hello.teardown = lambda: l.append(2)
""") """)
result = testdir.runpytest(p, '-p', 'nose') result = testdir.runpytest(p, '-p', 'nose')
result.stdout.fnmatch_lines([ result.assert_outcomes(passed=2)
"*2 passed*"
])
def test_setup_func_with_setup_decorator(): def test_setup_func_with_setup_decorator():
@ -66,9 +64,7 @@ def test_nose_setup_func(testdir):
""") """)
result = testdir.runpytest(p, '-p', 'nose') result = testdir.runpytest(p, '-p', 'nose')
result.stdout.fnmatch_lines([ result.assert_outcomes(passed=2)
"*2 passed*"
])
def test_nose_setup_func_failure(testdir): def test_nose_setup_func_failure(testdir):
@ -302,7 +298,7 @@ def test_apiwrapper_problem_issue260(testdir):
pass pass
""") """)
result = testdir.runpytest() result = testdir.runpytest()
result.stdout.fnmatch_lines("*1 passed*") result.assert_outcomes(passed=1)
@pytest.mark.skipif("sys.version_info < (2,6)") @pytest.mark.skipif("sys.version_info < (2,6)")
def test_setup_teardown_linking_issue265(testdir): def test_setup_teardown_linking_issue265(testdir):
@ -327,8 +323,8 @@ def test_setup_teardown_linking_issue265(testdir):
"""Undoes the setup.""" """Undoes the setup."""
raise Exception("should not call teardown for skipped tests") raise Exception("should not call teardown for skipped tests")
''') ''')
reprec = testdir.inline_run() reprec = testdir.runpytest()
reprec.assertoutcome(passed=1, skipped=1) reprec.assert_outcomes(passed=1, skipped=1)
def test_SkipTest_during_collection(testdir): def test_SkipTest_during_collection(testdir):
@ -339,7 +335,7 @@ def test_SkipTest_during_collection(testdir):
assert False assert False
""") """)
result = testdir.runpytest(p) result = testdir.runpytest(p)
result.assertoutcome(skipped=1) result.assert_outcomes(skipped=1)
def test_SkipTest_in_test(testdir): def test_SkipTest_in_test(testdir):

View File

@ -2,6 +2,13 @@
import py import py
import sys import sys
def runpdb_and_get_report(testdir, source):
p = testdir.makepyfile(source)
result = testdir.runpytest_inprocess("--pdb", p)
reports = result.reprec.getreports("pytest_runtest_logreport")
assert len(reports) == 3, reports # setup/call/teardown
return reports[1]
class TestPDB: class TestPDB:
def pytest_funcarg__pdblist(self, request): def pytest_funcarg__pdblist(self, request):
@ -14,7 +21,7 @@ class TestPDB:
return pdblist return pdblist
def test_pdb_on_fail(self, testdir, pdblist): def test_pdb_on_fail(self, testdir, pdblist):
rep = testdir.inline_runsource1('--pdb', """ rep = runpdb_and_get_report(testdir, """
def test_func(): def test_func():
assert 0 assert 0
""") """)
@ -24,7 +31,7 @@ class TestPDB:
assert tb[-1].name == "test_func" assert tb[-1].name == "test_func"
def test_pdb_on_xfail(self, testdir, pdblist): def test_pdb_on_xfail(self, testdir, pdblist):
rep = testdir.inline_runsource1('--pdb', """ rep = runpdb_and_get_report(testdir, """
import pytest import pytest
@pytest.mark.xfail @pytest.mark.xfail
def test_func(): def test_func():
@ -34,7 +41,7 @@ class TestPDB:
assert not pdblist assert not pdblist
def test_pdb_on_skip(self, testdir, pdblist): def test_pdb_on_skip(self, testdir, pdblist):
rep = testdir.inline_runsource1('--pdb', """ rep = runpdb_and_get_report(testdir, """
import pytest import pytest
def test_func(): def test_func():
pytest.skip("hello") pytest.skip("hello")
@ -43,7 +50,7 @@ class TestPDB:
assert len(pdblist) == 0 assert len(pdblist) == 0
def test_pdb_on_BdbQuit(self, testdir, pdblist): def test_pdb_on_BdbQuit(self, testdir, pdblist):
rep = testdir.inline_runsource1('--pdb', """ rep = runpdb_and_get_report(testdir, """
import bdb import bdb
def test_func(): def test_func():
raise bdb.BdbQuit raise bdb.BdbQuit
@ -260,7 +267,7 @@ class TestPDB:
def test_pdb_collection_failure_is_shown(self, testdir): def test_pdb_collection_failure_is_shown(self, testdir):
p1 = testdir.makepyfile("""xxx """) p1 = testdir.makepyfile("""xxx """)
result = testdir.runpytest("--pdb", p1) result = testdir.runpytest_subprocess("--pdb", p1)
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*NameError*xxx*", "*NameError*xxx*",
"*1 error*", "*1 error*",

View File

@ -69,9 +69,7 @@ def test_testdir_runs_with_plugin(testdir):
assert 1 assert 1
""") """)
result = testdir.runpytest() result = testdir.runpytest()
result.stdout.fnmatch_lines([ result.assert_outcomes(passed=1)
"*1 passed*"
])
def make_holder(): def make_holder():
@ -114,16 +112,6 @@ def test_makepyfile_unicode(testdir):
unichr = chr unichr = chr
testdir.makepyfile(unichr(0xfffd)) testdir.makepyfile(unichr(0xfffd))
def test_inprocess_plugins(testdir):
class Plugin(object):
configured = False
def pytest_configure(self, config):
self.configured = True
plugin = Plugin()
testdir.inprocess_run([], [plugin])
assert plugin.configured
def test_inline_run_clean_modules(testdir): def test_inline_run_clean_modules(testdir):
test_mod = testdir.makepyfile("def test_foo(): assert True") test_mod = testdir.makepyfile("def test_foo(): assert True")
result = testdir.inline_run(str(test_mod)) result = testdir.inline_run(str(test_mod))

View File

@ -203,7 +203,6 @@ class TestNewSession(SessionTests):
def test_plugin_specify(testdir): def test_plugin_specify(testdir):
testdir.chdir()
pytest.raises(ImportError, """ pytest.raises(ImportError, """
testdir.parseconfig("-p", "nqweotexistent") testdir.parseconfig("-p", "nqweotexistent")
""") """)

13
tox.ini
View File

@ -1,6 +1,6 @@
[tox] [tox]
distshare={homedir}/.tox/distshare distshare={homedir}/.tox/distshare
envlist=flakes,py26,py27,py34,pypy,py27-pexpect,py33-pexpect,py27-nobyte,py33,py27-xdist,py33-xdist,py27-trial,py33-trial,doctesting,py27-cxfreeze envlist=flakes,py26,py27,py34,pypy,py27-pexpect,py33-pexpect,py27-nobyte,py33,py27-xdist,py33-xdist,{py27,py33}-trial,py27-subprocess,doctesting,py27-cxfreeze
[testenv] [testenv]
changedir=testing changedir=testing
@ -9,6 +9,15 @@ deps=
nose nose
mock mock
[testenv:py27-subprocess]
changedir=.
basepython=python2.7
deps=pytest-xdist
mock
nose
commands=
py.test -n3 -rfsxX --runpytest=subprocess {posargs:testing}
[testenv:genscript] [testenv:genscript]
changedir=. changedir=.
commands= py.test --genscript=pytest1 commands= py.test --genscript=pytest1
@ -136,7 +145,7 @@ commands=
minversion=2.0 minversion=2.0
plugins=pytester plugins=pytester
#--pyargs --doctest-modules --ignore=.tox #--pyargs --doctest-modules --ignore=.tox
addopts= -rxsX addopts= -rxsX -p pytester
rsyncdirs=tox.ini pytest.py _pytest testing rsyncdirs=tox.ini pytest.py _pytest testing
python_files=test_*.py *_test.py testing/*/*.py python_files=test_*.py *_test.py testing/*/*.py
python_classes=Test Acceptance python_classes=Test Acceptance