merge trunk

This commit is contained in:
holger krekel 2010-10-31 18:17:08 +01:00
commit f73ab23003
33 changed files with 532 additions and 268 deletions

View File

@ -1,7 +1,11 @@
Changes between 1.3.4 and 2.0.0dev0
----------------------------------------------
- pytest-2.0 is now its own package and depends on pylib
- pytest-2.0 is now its own package and depends on pylib-2.0
- introduce a new way to set config options via ini-style files,
by default setup.cfg and tox.ini files are searched. The old
ways (certain environment variables, dynamic conftest.py reading
is removed).
- fix issue126 - introduce py.test.set_trace() to trace execution via
PDB during the running of tests even if capturing is ongoing.
- fix issue123 - new "python -m py.test" invocation for py.test

View File

@ -258,3 +258,9 @@ epub_copyright = u'2010, holger krekel et aliter'
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'http://docs.python.org/': None}
def setup(app):
#from sphinx.ext.autodoc import cut_lines
#app.connect('autodoc-process-docstring', cut_lines(4, what=['module']))
app.add_description_unit('confval', 'confval',
objname='configuration value',
indextemplate='pair: %s; configuration value')

View File

@ -5,33 +5,57 @@ Customizing and Extending py.test
basic test configuration
===================================
Command line options
---------------------------------
Command line options and configuration file settings
-----------------------------------------------------------------
You can see command line options by running::
You can get help on options and configuration options by running::
py.test -h
py.test -h # prints options _and_ config file settings
This will display all available command line options
in your specific environment.
This will display command line and configuration file settings
which were registered by installed plugins.
how test configuration is read from setup/tox ini-files
--------------------------------------------------------
setting persistent option defaults
------------------------------------
py.test looks for the first ``[pytest]`` section in either the first ``setup.cfg`` or the first ``tox.ini`` file found upwards from the arguments. Example::
py.test will lookup option values in this order:
py.test path/to/testdir
* command line
* conftest.py files
* environment variables
will look in the following dirs for a config file::
To get an overview on existing names and settings type::
path/to/testdir/setup.cfg
path/to/setup.cfg
path/setup.cfg
setup.cfg
... # up until root of filesystem
path/to/testdir/tox.ini
path/to/tox.ini
path/tox.ini
... # up until root of filesystem
py.test --help-config
If no path was provided at all the current working directory is used for the lookup.
This will print information about all available options
in your environment, including your local plugins and
command line options.
builtin configuration file options
----------------------------------------------
.. confval:: minversion = VERSTRING
specifies the minimal pytest version that is needed for this test suite.
minversion = 2.1 # will fail if we run with pytest-2.0
.. confval:: addargs = OPTS
add the specified ``OPTS`` to the set of command line arguments as if they
had been specified by the user. Example: if you have this ini file content::
[pytest]
addargs = --maxfail=2 -rf # exit after 2 failures, report fail info
issuing ``py.test test_hello.py`` actually means::
py.test --maxfail=2 -rf test_hello.py
.. _`function arguments`: funcargs.html
.. _`extensions`:
@ -49,10 +73,8 @@ extensions and customizations close to test code.
local conftest.py plugins
--------------------------------------------------------------
local `conftest.py` plugins are usually automatically loaded and
registered but its contained hooks are only called when collecting or
running tests in files or directories next to or below the ``conftest.py``
file. Assume the following layout and content of files::
local ``conftest.py`` plugins contain directory-specific hook implemenations. Its contained runtest- and collection- related hooks are called when collecting or running tests in files or directories next to or below the ``conftest.py``
file. Example: Assume the following layout and content of files::
a/conftest.py:
def pytest_runtest_setup(item):
@ -72,20 +94,22 @@ Here is how you might run it::
py.test a/test_sub.py # will show "setting up"
``py.test`` loads all ``conftest.py`` files upwards from the command
line file arguments. It usually looks up configuration values or hooks
right-to-left, i.e. the closer conftest files are checked before
the further away ones. This means you can have a ``conftest.py``
in your home directory to provide global configuration values.
line file arguments. It usually performs look up right-to-left, i.e.
the hooks in "closer" conftest files will be called earlier than further
away ones. This means you can even have a ``conftest.py`` file in your home
directory to customize test functionality globally for all of your projects.
.. Note::
if you have ``conftest.py`` files which do not reside in a
If you have ``conftest.py`` files which do not reside in a
python package directory (i.e. one containing an ``__init__.py``) then
"import conftest" will be ambigous and should be avoided. If you
ever want to import anything from a ``conftest.py`` file
put it inside a package. You avoid trouble this way.
"import conftest" can be ambigous because there might be other
``conftest.py`` files as well on your PYTHONPATH or ``sys.path``.
It is good practise for projects to put ``conftest.py`` within a package
scope or to never import anything from the conftest.py file.
.. _`named plugins`: plugin/index.html
Plugin discovery at tool startup
--------------------------------------------
@ -93,9 +117,6 @@ py.test loads plugin modules at tool startup in the following way:
* by loading all plugins registered through `setuptools entry points`_.
* by reading the ``PYTEST_PLUGINS`` environment variable
and importing the comma-separated list of named plugins.
* by pre-scanning the command line for the ``-p name`` option
and loading the specified plugin before actual command line parsing.
@ -105,39 +126,17 @@ py.test loads plugin modules at tool startup in the following way:
not loaded at tool startup.
* by recursively loading all plugins specified by the
``pytest_plugins`` variable in a ``conftest.py`` file
``pytest_plugins`` variable in ``conftest.py`` files
Requiring/Loading plugins in a test module or plugin
-------------------------------------------------------------
You can specify plugins in a test module or a plugin like this::
You can require plugins in a test module or a plugin like this::
pytest_plugins = "name1", "name2",
When the test module or plugin is loaded the specified plugins
will be loaded. If you specify plugins without the ``pytest_``
prefix it will be automatically added. All plugin names
must be lowercase.
.. _`conftest.py plugin`:
.. _`conftestplugin`:
Writing per-project plugins (conftest.py)
------------------------------------------------------
The purpose of :file:`conftest.py` files is to allow project-specific
test customization. They thus make for a good place to implement
project-specific test related features through hooks. For example you may
set the ``collect_ignore`` variable depending on a command line option
by defining the following hook in a ``conftest.py`` file::
# ./conftest.py in your root or package dir
collect_ignore = ['hello', 'test_world.py']
def pytest_addoption(parser):
parser.addoption("--runall", action="store_true", default=False)
def pytest_configure(config):
if config.getvalue("runall"):
collect_ignore[:] = []
will be loaded.
.. _`setuptools entry points`:
.. _registered:
@ -332,12 +331,28 @@ Reference of important objects involved in hooks
.. autoclass:: pytest.plugin.config.Config
:members:
.. autoclass:: pytest.plugin.session.Item
:inherited-members:
.. autoclass:: pytest.plugin.session.Node
.. autoclass:: pytest.plugin.config.Parser
:members:
.. autoclass:: pytest.plugin.session.Node(name, parent)
:members:
..
.. autoclass:: pytest.plugin.session.File(fspath, parent)
:members:
.. autoclass:: pytest.plugin.session.Item(name, parent)
:members:
.. autoclass:: pytest.plugin.python.Module(name, parent)
:members:
.. autoclass:: pytest.plugin.python.Class(name, parent)
:members:
.. autoclass:: pytest.plugin.python.Function(name, parent)
:members:
.. autoclass:: pytest.plugin.runner.CallInfo
:members:
@ -345,29 +360,3 @@ Reference of important objects involved in hooks
:members:
conftest.py configuration files
=================================================
conftest.py reference docs
A unique feature of py.test are its ``conftest.py`` files which allow
project and directory specific customizations to testing.
* `set option defaults`_
or set particular variables to influence the testing process:
* ``pytest_plugins``: list of named plugins to load
* ``collect_ignore``: list of paths to ignore during test collection, relative to the containing ``conftest.py`` file
* ``rsyncdirs``: list of to-be-rsynced directories for distributed
testing, relative to the containing ``conftest.py`` file.
You may put a conftest.py files in your project root directory or into
your package directory if you want to add project-specific test options.
.. _`specify funcargs`: funcargs.html#application-setup-tutorial-example
.. _`set option defaults`:

View File

@ -1,7 +1,4 @@
Customizing test function through marks and hooks
====================================================
.. _`retrieved by hooks as item keywords`:
control skipping of tests according to command line option

View File

@ -1,7 +1,4 @@
Misc examples
====================================================
Detect if running from within a py.test run
--------------------------------------------------------------

View File

@ -1,6 +1,8 @@
.. highlightlang:: python
.. _mysetup:
mysetup pattern: application specific test fixtures
==========================================================

68
doc/example/nonpython.txt Normal file
View File

@ -0,0 +1,68 @@
.. _`non-python tests`:
Working with non-python tests
====================================================
a basic example for specifying tests in Yaml files
--------------------------------------------------------------
.. _`pytest-yamlwsgi`: http://bitbucket.org/aafshar/pytest-yamlwsgi/src/tip/pytest_yamlwsgi.py
.. _`PyYAML`: http://pypi.python.org/pypi/PyYAML/
Here is an example ``conftest.py`` (extracted from Ali Afshnars special purpose `pytest-yamlwsgi`_ plugin). This ``conftest.py`` will collect ``test*.yml`` files and will execute the yaml-formatted content as custom tests:
.. include:: nonpython/conftest.py
:literal:
You can create a simple example file:
.. include:: nonpython/test_simple.yml
:literal:
and if you installed `PyYAML`_ or a compatible YAML-parser you can
now execute the test specification::
nonpython $ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev10
test path 1: /home/hpk/p/pytest/doc/example/nonpython
test_simple.yml .F
================================= FAILURES =================================
______________________________ usecase: hello ______________________________
usecase execution failed
spec failed: 'some': 'other'
no further details known at this point.
==================== 1 failed, 1 passed in 0.06 seconds ====================
You get one dot for the passing ``sub1: sub1`` check and one failure.
Obviously in the above ``conftest.py`` you'll want to implement a more
interesting interpretation of the yaml-values. Note that ``reportinfo()``
is used for representing the test location and is also consulted for
reporting in ``verbose`` mode::
nonpython $ py.test -v
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev10 -- /home/hpk/venv/0/bin/python
test path 1: /home/hpk/p/pytest/doc/example/nonpython
test_simple.yml:1: usecase: ok PASSED
test_simple.yml:1: usecase: hello FAILED
================================= FAILURES =================================
______________________________ usecase: hello ______________________________
usecase execution failed
spec failed: 'some': 'other'
no further details known at this point.
==================== 1 failed, 1 passed in 0.06 seconds ====================
While developing your custom test collection and execution it's also
interesting to just look at the collection tree::
nonpython $ py.test --collectonly
<Directory 'nonpython'>
<YamlFile 'test_simple.yml'>
<UsecaseItem 'ok'>
<UsecaseItem 'hello'>

View File

@ -0,0 +1,40 @@
# content of conftest.py
import py
def pytest_collect_file(path, parent):
if path.ext == ".yml" and path.basename.startswith("test"):
return YamlFile(path, parent)
class YamlFile(py.test.collect.File):
def collect(self):
import yaml # we need a yaml parser, e.g. PyYAML
raw = yaml.load(self.fspath.open())
for name, spec in raw.items():
yield UsecaseItem(name, self, spec)
class UsecaseItem(py.test.collect.Item):
def __init__(self, name, parent, spec):
super(UsecaseItem, self).__init__(name, parent)
self.spec = spec
def runtest(self):
for name, value in self.spec.items():
# some custom test execution (dumb example follows)
if name != value:
raise UsecaseException(self, name, value)
def repr_failure(self, excinfo):
""" called when self.runtest() raises an exception. """
if excinfo.errisinstance(UsecaseException):
return "\n".join([
"usecase execution failed",
" spec failed: %r: %r" % excinfo.value.args[1:3],
" no further details known at this point."
])
def reportinfo(self):
return self.fspath, 0, "usecase: %s" % self.name
class UsecaseException(Exception):
""" custom exception for error reporting. """

View File

@ -0,0 +1,7 @@
# test_simple.yml
ok:
sub1: sub1
hello:
world: world
some: other

View File

@ -7,6 +7,7 @@ Usages and Examples
.. toctree::
:maxdepth: 2
example/marking.txt
example/controlskip.txt
example/mysetup.txt
example/misc.txt
example/detectpytest.txt
example/nonpython.txt

View File

@ -45,7 +45,8 @@ supports several testing practises and methods
- supports extended `xUnit style setup`_
- can integrate nose_, `unittest.py` and `doctest.py`_ style tests
- supports generating testing coverage
- supports generating testing coverage reports
- supports :ref:`non-python tests`
- `Javasript unit- and functional testing`_
.. _`Javasript unit- and functional testing`: plugin/oejskit.html

View File

@ -1,4 +1,5 @@
.. highlightlang:: python
.. _`good practises`:
Good Practises
@ -28,8 +29,7 @@ py.test supports common test layouts.
XXX
.. _`genscript method`:
Generating a py.test standalone Script
-------------------------------------------
@ -38,12 +38,12 @@ If you are a maintainer or application developer and want users
to run tests you can use a facility to generate a standalone
"py.test" script that you can tell users to run::
py.test --genscript=mytest
py.test --genscript=runtests.py
will generate a ``mytest`` script that is, in fact, a ``py.test`` under
disguise. You can tell people to download and then e.g. run it like this::
python mytest --pastebin=all
python runtests.py --pastebin=all
and ask them to send you the resulting URL. The resulting script has
all core features and runs unchanged under Python2 and Python3 interpreters.
@ -51,4 +51,46 @@ all core features and runs unchanged under Python2 and Python3 interpreters.
.. _`Distribute for installation`: http://pypi.python.org/pypi/distribute#installation-instructions
.. _`distribute installation`: http://pypi.python.org/pypi/distribute
Integrating with distutils / ``python setup.py test``
--------------------------------------------------------
You can easily integrate test runs into your distutils or
setuptools based project. Use the `genscript method`_
to generate a standalone py.test script::
py.test --genscript=runtests.py
and make this script part of your distribution and then add
this to your ``setup.py`` file::
from distutils.core import setup, Command
# you can also import from setuptools
class PyTest(Command):
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
import sys,subprocess
errno = subprocess.call([sys.executable, 'runtest.py'])
raise SystemExit(errno)
setup(
#...,
cmdclass = {'test': PyTest},
#...,
)
If you now type::
python setup.py test
this will execute your tests using ``runtest.py``. As this is a
standalone version of ``py.test`` no prior installation whatsoever is
required for calling the test command. You can also pass additional
arguments to the subprocess-calls like your test directory or other
options.
.. include:: links.inc

View File

@ -16,8 +16,8 @@ basic usage and funcargs:
function arguments:
- :ref:`mysetup`
- `application setup in test functions with funcargs`_
- `making funcargs dependendent on command line options`_
- `monkey patching done right`_ (blog post, consult `monkeypatch
plugin`_ for actual 1.0 API)
@ -39,7 +39,6 @@ plugin specific examples:
- `many examples in the docs for plugins`_
.. _`skipping slow tests by default in py.test`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html
.. _`making funcargs dependendent on command line options`: funcargs.html#tut-cmdlineoption
.. _`many examples in the docs for plugins`: plugin/index.html
.. _`monkeypatch plugin`: plugin/monkeypatch.html
.. _`application setup in test functions with funcargs`: funcargs.html#appsetup

View File

@ -2,8 +2,6 @@
import py
failure_demo = py.path.local(__file__).dirpath('failure_demo.py')
pytest_plugins = "pytest_pytester"
def test_failure_demo_fails_properly(testdir):
target = testdir.tmpdir.join(failure_demo.basename)
failure_demo.copy(target)

View File

@ -5,11 +5,12 @@ see http://pytest.org for documentation and details
(c) Holger Krekel and others, 2004-2010
"""
__version__ = '2.0.0.dev10'
__version__ = '2.0.0.dev11'
__all__ = ['config', 'cmdline']
from pytest import _core as cmdline
UsageError = cmdline.UsageError
def __main__():
raise SystemExit(cmdline.main())

View File

@ -345,13 +345,16 @@ def main(args=None):
if args is None:
args = sys.argv[1:]
hook = pluginmanager.hook
config = hook.pytest_cmdline_parse(pluginmanager=pluginmanager, args=args)
try:
config = hook.pytest_cmdline_parse(
pluginmanager=pluginmanager, args=args)
exitstatus = hook.pytest_cmdline_main(config=config)
except config.Error:
except UsageError:
e = sys.exc_info()[1]
sys.stderr.write("ERROR: %s\n" %(e.args[0],))
exitstatus = 3
pluginmanager = PluginManager(load=True)
return exitstatus
class UsageError(Exception):
""" error in py.test usage or invocation"""

View File

@ -2,6 +2,7 @@
import py
import sys, os
from pytest._core import PluginManager
import pytest
def pytest_cmdline_parse(pluginmanager, args):
@ -9,6 +10,10 @@ def pytest_cmdline_parse(pluginmanager, args):
config.parse(args)
return config
def pytest_addoption(parser):
parser.addini('addargs', 'default command line arguments')
parser.addini('minversion', 'minimally required pytest version')
class Parser:
""" Parser for command line arguments. """
@ -17,6 +22,7 @@ class Parser:
self._groups = []
self._processopt = processopt
self._usage = usage
self._inidict = {}
self.hints = []
def processoption(self, option):
@ -28,6 +34,12 @@ class Parser:
self._notes.append(note)
def getgroup(self, name, description="", after=None):
""" get (or create) a named option Group.
:name: unique name of the option group.
:description: long description for --help output.
:after: name of other group, used for ordering --help output.
"""
for group in self._groups:
if group.name == name:
return group
@ -44,7 +56,7 @@ class Parser:
self._anonymous.addoption(*opts, **attrs)
def parse(self, args):
optparser = MyOptionParser(self)
self.optparser = optparser = MyOptionParser(self)
groups = self._groups + [self._anonymous]
for group in groups:
if group.options:
@ -52,7 +64,7 @@ class Parser:
optgroup = py.std.optparse.OptionGroup(optparser, desc)
optgroup.add_options(group.options)
optparser.add_option_group(optgroup)
return optparser.parse_args([str(x) for x in args])
return self.optparser.parse_args([str(x) for x in args])
def parse_setoption(self, args, option):
parsedoption, args = self.parse(args)
@ -60,6 +72,9 @@ class Parser:
setattr(option, name, value)
return args
def addini(self, name, description, type=None):
""" add an ini-file option with the given name and description. """
self._inidict[name] = (description, type)
class OptionGroup:
def __init__(self, name, description="", parser=None):
@ -90,7 +105,8 @@ class OptionGroup:
class MyOptionParser(py.std.optparse.OptionParser):
def __init__(self, parser):
self._parser = parser
py.std.optparse.OptionParser.__init__(self, usage=parser._usage)
py.std.optparse.OptionParser.__init__(self, usage=parser._usage,
add_help_option=False)
def format_epilog(self, formatter):
hints = self._parser.hints
if hints:
@ -226,13 +242,8 @@ class CmdOptions(object):
def __repr__(self):
return "<CmdOptions %r>" %(self.__dict__,)
class Error(Exception):
""" Test Configuration Error. """
class Config(object):
""" access to configuration values, pluginmanager and plugin hooks. """
Option = py.std.optparse.Option
Error = Error
basetemp = None
def __init__(self, pluginmanager=None):
@ -251,6 +262,11 @@ class Config(object):
self.trace("loaded conftestmodule %r" %(conftestmodule,))
self.pluginmanager.consider_conftest(conftestmodule)
def _processopt(self, opt):
if hasattr(opt, 'default') and opt.dest:
if not hasattr(self.option, opt.dest):
setattr(self.option, opt.dest, opt.default)
def _getmatchingplugins(self, fspath):
allconftests = self._conftest._conftestpath2mod.values()
plugins = [x for x in self.pluginmanager.getplugins()
@ -262,28 +278,6 @@ class Config(object):
if getattr(self.option, 'traceconfig', None):
self.hook.pytest_trace(category="config", msg=msg)
def _processopt(self, opt):
if hasattr(opt, 'default') and opt.dest:
val = os.environ.get("PYTEST_OPTION_" + opt.dest.upper(), None)
if val is not None:
if opt.type == "int":
val = int(val)
elif opt.type == "long":
val = long(val)
elif opt.type == "float":
val = float(val)
elif not opt.type and opt.action in ("store_true", "store_false"):
val = eval(val)
opt.default = val
else:
name = "option_" + opt.dest
try:
opt.default = self._conftest.rget(name)
except (ValueError, KeyError):
pass
if not hasattr(self.option, opt.dest):
setattr(self.option, opt.dest, opt.default)
def _setinitialconftest(self, args):
# capture output during conftest init (#issue93)
name = hasattr(os, 'dup') and 'StdCaptureFD' or 'StdCapture'
@ -299,14 +293,31 @@ class Config(object):
raise
def _preparse(self, args):
self.inicfg = getcfg(args, ["setup.cfg", "tox.ini",])
if self.inicfg:
newargs = self.inicfg.get("addargs", None)
if newargs:
args[:] = args + py.std.shlex.split(newargs)
self._checkversion()
self.pluginmanager.consider_setuptools_entrypoints()
self.pluginmanager.consider_env()
self.pluginmanager.consider_preparse(args)
self._setinitialconftest(args)
self.pluginmanager.do_addoption(self._parser)
def _checkversion(self):
minver = self.inicfg.get('minversion', None)
if minver:
ver = minver.split(".")
myver = pytest.__version__.split(".")
if myver < ver:
raise pytest.UsageError(
"%s:%d: requires pytest-%s, actual pytest-%s'" %(
self.inicfg.config.path, self.inicfg.lineof('minversion'),
minver, pytest.__version__))
def parse(self, args):
# cmdline arguments into this config object.
# parse given cmdline arguments into this config object.
# Note that this can only be called once per testing process.
assert not hasattr(self, 'args'), (
"can only parse cmdline args at most once per Config object")
@ -340,12 +351,28 @@ class Config(object):
return py.path.local.make_numbered_dir(prefix=basename,
keep=0, rootdir=basetemp, lock_timeout=None)
def getconftest_pathlist(self, name, path=None):
""" return a matching value, which needs to be sequence
of filenames that will be returned as a list of Path
objects (they can be relative to the location
where they were found).
"""
def getini(self, name):
""" return configuration value from an ini file. If the
specified name hasn't been registered through a prior ``parse.addini``
call (usually from a plugin), a ValueError is raised. """
try:
description, type = self._parser._inidict[name]
except KeyError:
raise ValueError("unknown configuration value: %r" %(name,))
try:
value = self.inicfg[name]
except KeyError:
return # None indicates nothing found
if type == "pathlist":
dp = py.path.local(self.inicfg.config.path).dirpath()
l = []
for relpath in py.std.shlex.split(value):
l.append(dp.join(relpath, abs=True))
return l
else:
return value
def _getconftest_pathlist(self, name, path=None):
try:
mod, relroots = self._conftest.rget_with_confmod(name, path)
except KeyError:
@ -359,6 +386,22 @@ class Config(object):
l.append(relroot)
return l
def _getconftest(self, name, path=None, check=False):
if check:
self._checkconftest(name)
return self._conftest.rget(name, path)
def getvalue(self, name, path=None):
""" return ``name`` value looked set from command line options.
(deprecated) if we can't find the option also lookup
the name in a matching conftest file.
"""
try:
return getattr(self.option, name)
except AttributeError:
return self._getconftest(name, path, check=False)
def getvalueorskip(self, name, path=None):
""" return getvalue(name) or call py.test.skip if no value exists. """
try:
@ -369,15 +412,27 @@ class Config(object):
except KeyError:
py.test.skip("no %r value found" %(name,))
def getvalue(self, name, path=None):
""" return 'name' value looked up from the 'options'
and then from the first conftest file found up
the path (including the path itself).
if path is None, lookup the value in the initial
conftest modules found during command line parsing.
"""
try:
return getattr(self.option, name)
except AttributeError:
return self._conftest.rget(name, path)
def getcfg(args, inibasenames):
if not args:
args = [py.path.local()]
for inibasename in inibasenames:
for p in args:
x = findupwards(p, inibasename)
if x is not None:
iniconfig = py.iniconfig.IniConfig(x)
if 'pytest' in iniconfig.sections:
return iniconfig['pytest']
return {}
def findupwards(current, basename):
current = py.path.local(current)
while 1:
p = current.join(basename)
if p.check():
return p
p = current.dirpath()
if p == current:
return
current = p

View File

@ -58,7 +58,7 @@ def pytest_cmdline_main(config):
genscript = config.getvalue("genscript")
if genscript:
script = generate_script(
'import py; py.test.cmdline.main()',
'import py; raise SystemExit(py.test.cmdline.main())',
['py', 'pytest'],
)

View File

@ -1,12 +1,15 @@
""" provide version info, conftest/environment config names.
"""
import py
import pytest
import inspect, sys
def pytest_addoption(parser):
group = parser.getgroup('debugconfig')
group.addoption('--version', action="store_true",
help="display py lib version and import information.")
help="display pytest lib version and import information.")
group._addoption("-h", "--help", action="store_true", dest="help",
help="show help message and configuration info")
group._addoption('-p', action="append", dest="plugins", default = [],
metavar="name",
help="early-load given plugin (multi-allowed).")
@ -19,67 +22,55 @@ def pytest_addoption(parser):
group.addoption('--debug',
action="store_true", dest="debug", default=False,
help="generate and show internal debugging information.")
group.addoption("--help-config", action="store_true", dest="helpconfig",
help="show available conftest.py and ENV-variable names.")
def pytest_cmdline_main(config):
if config.option.version:
p = py.path.local(py.__file__).dirpath()
p = py.path.local(pytest.__file__).dirpath()
sys.stderr.write("This is py.test version %s, imported from %s\n" %
(py.__version__, p))
return 0
elif config.option.helpconfig:
elif config.option.help:
config.pluginmanager.do_configure(config)
showpluginhelp(config)
showhelp(config)
return 0
def showpluginhelp(config):
options = []
for group in config._parser._groups:
options.extend(group.options)
widths = [0] * 10
def showhelp(config):
tw = py.io.TerminalWriter()
tw.sep("-")
tw.line("%-13s | %-18s | %-25s | %s" %(
"cmdline name", "conftest.py name", "ENV-variable name", "help"))
tw.sep("-")
tw.write(config._parser.optparser.format_help())
tw.line()
tw.line()
#tw.sep( "=", "config file settings")
tw.line("setup.cfg or tox.ini options to be put into [pytest] section:")
tw.line()
options = [opt for opt in options if opt._long_opts]
options.sort(key=lambda x: x._long_opts)
for opt in options:
if not opt._long_opts or not opt.dest:
continue
optstrings = list(opt._long_opts) # + list(opt._short_opts)
optstrings = filter(None, optstrings)
optstring = "|".join(optstrings)
line = "%-13s | %-18s | %-25s | %s" %(
optstring,
"option_%s" % opt.dest,
"PYTEST_OPTION_%s" % opt.dest.upper(),
opt.help and opt.help or "",
)
for name, help in sorted(config._parser._inidict.items()):
line = " %-15s %s" %(name, help)
tw.line(line[:tw.fullwidth])
for name, help in conftest_options:
line = "%-13s | %-18s | %-25s | %s" %(
"",
name,
"",
help,
)
tw.line(line[:tw.fullwidth])
tw.sep("-")
conftest_options = (
tw.line() ; tw.line()
#tw.sep("=")
return
tw.line("conftest.py options:")
tw.line()
conftestitems = sorted(config._parser._conftestdict.items())
for name, help in conftest_options + conftestitems:
line = " %-15s %s" %(name, help)
tw.line(line[:tw.fullwidth])
tw.line()
#tw.sep( "=")
conftest_options = [
('pytest_plugins', 'list of plugin names to load'),
('collect_ignore', '(relative) paths ignored during collection'),
('rsyncdirs', 'to-be-rsynced directories for dist-testing'),
)
]
def pytest_report_header(config):
lines = []
if config.option.debug or config.option.traceconfig:
lines.append("using py lib: %s" % (py.path.local(py.__file__).dirpath()))
lines.append("using: pytest-%s pylib-%s" %
(pytest.__version__,py.__version__))
if config.option.traceconfig:
lines.append("active plugins:")
plugins = []
@ -149,12 +140,11 @@ def getargs(func):
startindex = inspect.ismethod(func) and 1 or 0
return args[startindex:]
def collectattr(obj, prefixes=("pytest_",)):
def collectattr(obj):
methods = {}
for apiname in dir(obj):
for prefix in prefixes:
if apiname.startswith(prefix):
methods[apiname] = getattr(obj, apiname)
if apiname.startswith("pytest_"):
methods[apiname] = getattr(obj, apiname)
return methods
def formatdef(func):

View File

@ -242,9 +242,19 @@ class TmpTestdir:
def makefile(self, ext, *args, **kwargs):
return self._makefile(ext, args, kwargs)
def makeini(self, source):
return self.makefile('cfg', setup=source)
def makeconftest(self, source):
return self.makepyfile(conftest=source)
def makeini(self, source):
return self.makefile('.ini', tox=source)
def getinicfg(self, source):
p = self.makeini(source)
return py.iniconfig.IniConfig(p)['pytest']
def makepyfile(self, *args, **kwargs):
return self._makefile('.py', args, kwargs)
@ -375,6 +385,11 @@ class TmpTestdir:
#config.pluginmanager.do_unconfigure(config)
return node
def collect_by_name(self, modcol, name):
for colitem in modcol._memocollect():
if colitem.name == name:
return colitem
def popen(self, cmdargs, stdout, stderr, **kw):
if not hasattr(py.std, 'subprocess'):
py.test.skip("no subprocess module")

View File

@ -9,6 +9,7 @@ import pytest
import os, sys
def pytest_addoption(parser):
group = parser.getgroup("general", "running and selection options")
group._addoption('-x', '--exitfirst', action="store_true", default=False,
dest="exitfirst",
@ -32,6 +33,7 @@ def pytest_addoption(parser):
group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
help="base temporary directory for this test run.")
def pytest_namespace():
return dict(collect=dict(Item=Item, Collector=Collector,
File=File, Directory=Directory))
@ -64,7 +66,7 @@ def pytest_runtest_mainloop(session):
def pytest_ignore_collect(path, config):
p = path.dirpath()
ignore_paths = config.getconftest_pathlist("collect_ignore", path=p)
ignore_paths = config._getconftest_pathlist("collect_ignore", path=p)
ignore_paths = ignore_paths or []
excludeopt = config.getvalue("ignore")
if excludeopt:
@ -128,7 +130,7 @@ class Session(object):
config.hook.pytest_sessionstart(session=self)
config.hook.pytest_perform_collection(session=self)
config.hook.pytest_runtest_mainloop(session=self)
except self.config.Error:
except pytest.UsageError:
raise
except KeyboardInterrupt:
excinfo = py.code.ExceptionInfo()
@ -173,10 +175,10 @@ class Collection:
parts = str(arg).split("::")
path = base.join(parts[0], abs=True)
if not path.check():
raise self.config.Error("file not found: %s" %(path,))
raise pytest.UsageError("file not found: %s" %(path,))
topdir = self.topdir
if path != topdir and not path.relto(topdir):
raise self.config.Error("path %r is not relative to %r" %
raise pytest.UsageError("path %r is not relative to %r" %
(str(path), str(topdir)))
topparts = path.relto(topdir).split(path.sep)
return topparts + parts[1:]
@ -213,7 +215,7 @@ class Collection:
for node in self.matchnodes([self._topcollector], names):
items.extend(self.genitems(node))
except NoMatch:
raise self.config.Error("can't collect: %s" % (arg,))
raise pytest.UsageError("can't collect: %s" % (arg,))
return items
def matchnodes(self, matching, names):
@ -444,14 +446,8 @@ class Collector(Node):
"""
raise NotImplementedError("abstract")
def collect_by_name(self, name):
""" return a child matching the given name, else None. """
for colitem in self._memocollect():
if colitem.name == name:
return colitem
def repr_failure(self, excinfo):
""" represent a failure. """
""" represent a collection failure. """
if excinfo.errisinstance(self.CollectError):
exc = excinfo.value
return str(exc.args[0])
@ -524,8 +520,7 @@ class Directory(FSCollector):
class Item(Node):
""" a basic test invocation item. Note that for a single function
there might be multiple test invocation items. Attributes:
there might be multiple test invocation items.
"""
def reportinfo(self):
return self.fspath, None, ""

View File

@ -22,7 +22,7 @@ def main():
name='pytest',
description='py.test: simple powerful testing with Python',
long_description = long_description,
version='2.0.0.dev10',
version='2.0.0.dev11',
url='http://pytest.org',
license='MIT license',
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],

View File

@ -4,7 +4,8 @@ class TestGeneralUsage:
def test_config_error(self, testdir):
testdir.makeconftest("""
def pytest_configure(config):
raise config.Error("hello")
import pytest
raise pytest.UsageError("hello")
""")
result = testdir.runpytest(testdir.tmpdir)
assert result.ret != 0

View File

@ -2,9 +2,6 @@ import py
import sys
pytest_plugins = "pytester",
collect_ignore = ['../build', '../doc/_build']
rsyncdirs = ['conftest.py', '../pytest', '../doc', '.']
import os, py
pid = os.getpid()
@ -41,6 +38,12 @@ def pytest_unconfigure(config, __multicall__):
assert len2 < config._numfiles + 7, out2
def pytest_runtest_setup(item):
item._oldir = py.path.local()
def pytest_runtest_teardown(item):
item._oldir.chdir()
def pytest_generate_tests(metafunc):
multi = getattr(metafunc.function, 'multi', None)
if multi is not None:

View File

@ -1,6 +1,5 @@
import py
pytest_plugins = "pytester"
import pytest.plugin
plugindir = py.path.local(pytest.plugin.__file__).dirpath()
from pytest._core import default_plugins

View File

@ -19,13 +19,14 @@ class Standalone:
return testdir._run(anypython, self.script, *args)
def test_gen(testdir, anypython, standalone):
result = standalone.run(anypython, testdir, '-h')
assert result.ret == 0
result = standalone.run(anypython, testdir, '--version')
assert result.ret == 0
result.stderr.fnmatch_lines([
"*imported from*mypytest"
])
p = testdir.makepyfile("def test_func(): assert 0")
result = standalone.run(anypython, testdir, p)
assert result.ret != 0
@py.test.mark.xfail(reason="fix-dist", run=False)
def test_rundist(testdir, pytestconfig, standalone):

View File

@ -10,11 +10,13 @@ def test_version(testdir):
'*py.test*%s*imported from*' % (py.version, )
])
def test_helpconfig(testdir):
result = testdir.runpytest("--help-config")
def test_help(testdir):
result = testdir.runpytest("--help")
assert result.ret == 0
result.stdout.fnmatch_lines([
"*cmdline*conftest*ENV*",
"*-v*verbose*",
"*setup.cfg*",
"*minversion*",
])
def test_collectattr():

View File

@ -268,9 +268,9 @@ class TestSorting:
def test_pass(): pass
def test_fail(): assert 0
""")
fn1 = modcol.collect_by_name("test_pass")
fn1 = testdir.collect_by_name(modcol, "test_pass")
assert isinstance(fn1, py.test.collect.Function)
fn2 = modcol.collect_by_name("test_pass")
fn2 = testdir.collect_by_name(modcol, "test_pass")
assert isinstance(fn2, py.test.collect.Function)
assert fn1 == fn2
@ -279,7 +279,7 @@ class TestSorting:
assert cmp(fn1, fn2) == 0
assert hash(fn1) == hash(fn2)
fn3 = modcol.collect_by_name("test_fail")
fn3 = testdir.collect_by_name(modcol, "test_fail")
assert isinstance(fn3, py.test.collect.Function)
assert not (fn1 == fn3)
assert fn1 != fn3
@ -1092,7 +1092,7 @@ class TestReportInfo:
class TestClass:
def test_hello(self): pass
""")
classcol = modcol.collect_by_name("TestClass")
classcol = testdir.collect_by_name(modcol, "TestClass")
fspath, lineno, msg = classcol.reportinfo()
assert fspath == modcol.fspath
assert lineno == 1
@ -1106,7 +1106,7 @@ class TestReportInfo:
assert x
yield check, 3
""")
gencol = modcol.collect_by_name("test_gen")
gencol = testdir.collect_by_name(modcol, "test_gen")
fspath, lineno, modpath = gencol.reportinfo()
assert fspath == modcol.fspath
assert lineno == 1

View File

@ -466,8 +466,8 @@ def test_getreportopt():
testdict.update(dict(reportchars="sfx"))
assert getreportopt(config) == "sfx"
def test_terminalreporter_reportopt_conftestsetting(testdir):
testdir.makeconftest("option_report = 'skipped'")
def test_terminalreporter_reportopt_addargs(testdir):
testdir.makeini("[pytest]\naddargs=-rs")
p = testdir.makepyfile("""
def pytest_funcarg__tr(request):
tr = request.config.pluginmanager.getplugin("terminalreporter")

View File

@ -28,9 +28,9 @@ class TestCollector:
def test_pass(): pass
def test_fail(): assert 0
""")
fn1 = modcol.collect_by_name("test_pass")
fn1 = testdir.collect_by_name(modcol, "test_pass")
assert isinstance(fn1, py.test.collect.Function)
fn2 = modcol.collect_by_name("test_pass")
fn2 = testdir.collect_by_name(modcol, "test_pass")
assert isinstance(fn2, py.test.collect.Function)
assert fn1 == fn2
@ -39,7 +39,7 @@ class TestCollector:
assert cmp(fn1, fn2) == 0
assert hash(fn1) == hash(fn2)
fn3 = modcol.collect_by_name("test_fail")
fn3 = testdir.collect_by_name(modcol, "test_fail")
assert isinstance(fn3, py.test.collect.Function)
assert not (fn1 == fn3)
assert fn1 != fn3
@ -57,8 +57,9 @@ class TestCollector:
def test_foo():
pass
""")
cls = modcol.collect_by_name("TestClass")
fn = cls.collect_by_name("()").collect_by_name("test_foo")
cls = testdir.collect_by_name(modcol, "TestClass")
fn = testdir.collect_by_name(
testdir.collect_by_name(cls, "()"), "test_foo")
parent = fn.getparent(py.test.collect.Module)
assert parent is modcol

View File

@ -1,32 +1,42 @@
import py
class TestConfigCmdlineParsing:
def test_parser_addoption_default_env(self, testdir, monkeypatch):
import os
config = testdir.Config()
group = config._parser.getgroup("hello")
from pytest.plugin.config import getcfg, Config
monkeypatch.setitem(os.environ, 'PYTEST_OPTION_OPTION1', 'True')
group.addoption("--option1", action="store_true")
assert group.options[0].default == True
class TestParseIni:
def test_getcfg_and_config(self, tmpdir):
sub = tmpdir.mkdir("sub")
sub.chdir()
tmpdir.join("setup.cfg").write(py.code.Source("""
[pytest]
name = value
"""))
cfg = getcfg([sub], ["setup.cfg"])
assert cfg['name'] == "value"
config = Config()
config._preparse([sub])
assert config.inicfg['name'] == 'value'
monkeypatch.setitem(os.environ, 'PYTEST_OPTION_OPTION2', 'abc')
group.addoption("--option2", action="store", default="x")
assert group.options[1].default == "abc"
monkeypatch.setitem(os.environ, 'PYTEST_OPTION_OPTION3', '32')
group.addoption("--option3", action="store", type="int")
assert group.options[2].default == 32
group.addoption("--option4", action="store", type="int")
assert group.options[3].default == ("NO", "DEFAULT")
def test_parser_addoption_default_conftest(self, testdir, monkeypatch):
import os
testdir.makeconftest("option_verbose=True")
config = testdir.parseconfig()
def test_append_parse_args(self, tmpdir):
tmpdir.join("setup.cfg").write(py.code.Source("""
[pytest]
addargs = --verbose
"""))
config = Config()
config.parse([tmpdir])
assert config.option.verbose
def test_tox_ini_wrong_version(self, testdir):
p = testdir.makefile('.ini', tox="""
[pytest]
minversion=9.0
""")
result = testdir.runpytest()
assert result.ret != 0
result.stderr.fnmatch_lines([
"*tox.ini:2*requires*9.0*actual*"
])
class TestConfigCmdlineParsing:
def test_parsing_again_fails(self, testdir):
config = testdir.reparseconfig([testdir.tmpdir])
py.test.raises(AssertionError, "config.parse([])")
@ -97,13 +107,43 @@ class TestConfigAPI:
p = tmpdir.join("conftest.py")
p.write("pathlist = ['.', %r]" % str(somepath))
config = testdir.reparseconfig([p])
assert config.getconftest_pathlist('notexist') is None
pl = config.getconftest_pathlist('pathlist')
assert config._getconftest_pathlist('notexist') is None
pl = config._getconftest_pathlist('pathlist')
print(pl)
assert len(pl) == 2
assert pl[0] == tmpdir
assert pl[1] == somepath
def test_addini(self, testdir):
testdir.makeconftest("""
def pytest_addoption(parser):
parser.addini("myname", "my new ini value")
""")
testdir.makeini("""
[pytest]
myname=hello
""")
config = testdir.parseconfig()
val = config.getini("myname")
assert val == "hello"
py.test.raises(ValueError, config.getini, 'other')
def test_addini_pathlist(self, testdir):
testdir.makeconftest("""
def pytest_addoption(parser):
parser.addini("paths", "my new ini value", type="pathlist")
parser.addini("abc", "abc value")
""")
p = testdir.makeini("""
[pytest]
paths=hello world/sub.py
""")
config = testdir.parseconfig()
l = config.getini("paths")
assert len(l) == 2
assert l[0] == p.dirpath('hello')
assert l[1] == p.dirpath('world/sub.py')
py.test.raises(ValueError, config.getini, 'other')
def test_options_on_small_file_do_not_blow_up(testdir):
def runfiletest(opts):
@ -140,3 +180,4 @@ def test_preparse_ordering(testdir, monkeypatch):
config = testdir.parseconfig()
plugin = config.pluginmanager.getplugin("mytestplugin")
assert plugin.x == 42

View File

@ -1,12 +1,13 @@
import py
from pytest.plugin import config as parseopt
from textwrap import dedent
class TestParser:
def test_init(self, capsys):
def test_no_help_by_default(self, capsys):
parser = parseopt.Parser(usage="xyz")
py.test.raises(SystemExit, 'parser.parse(["-h"])')
out, err = capsys.readouterr()
assert out.find("xyz") != -1
assert err.find("no such option") != -1
def test_group_add_and_get(self):
parser = parseopt.Parser()
@ -100,6 +101,7 @@ class TestParser:
assert option.hello == "world"
assert option.this == 42
@py.test.mark.skipif("sys.version_info < (2,5)")
def test_addoption_parser_epilog(testdir):
testdir.makeconftest("""

View File

@ -48,3 +48,7 @@ changedir=testing
commands=
{envpython} {envbindir}/py.test-jython --no-tools-on-path \
-rfsxX --junitxml={envlogdir}/junit-{envname}2.xml [acceptance_test.py plugin]
[pytest]
minversion=2.0
plugins=pytester