fix issue30 (the second time)

put module globals into namespace for xfail and skipif expressions
This commit is contained in:
holger krekel 2011-03-03 23:22:55 +01:00
parent 682773e0cb
commit 070c73ff2f
7 changed files with 102 additions and 19 deletions

View File

@ -1,15 +1,37 @@
Changes between 2.0.1 and 2.0.2 Changes between 2.0.1 and 2.0.2
---------------------------------------------- ----------------------------------------------
- fix issue30 - better handling and error reporting for errors in xfail - fix issue30 - extended xfail/skipif handling and better reporting.
expressions If you have a syntax error in your skip/xfail
expressions you now get nice error reports.
Also you can now access module globals from xfail/skipif
expressions so that this works now::
import mymodule
@pytest.mark.skipif("mymodule.__version__[0] == "1")
def test_function():
pass
This will not run the test function if the module's version string
does not start with a "1". Note that specifying a string instead
of a boolean expressions allows py.test to report meaningful information
when summarizing a test run as to what conditions lead to skipping
(or xfail-ing) tests.
- fix issue28 - setup_method and pytest_generate_tests work together - fix issue28 - setup_method and pytest_generate_tests work together
The setup_method fixture method now gets called also for
test function invocations generated from the pytest_generate_tests
hook.
- fix issue23 - tmpdir argument now works on Python3.2 and WindowsXP - fix issue23 - tmpdir argument now works on Python3.2 and WindowsXP
(which apparently starts to offer os.symlink now) Starting with Python3.2 os.symlink may be supported. By requiring
a newer py lib version the py.path.local() implementation acknowledges
this.
- fixed some typos in the docs (thanks Victor) - fixed typos in the docs (thanks Victor, Brianna) and particular
thanks to Laura who also revieved the documentation which
lead to some improvements.
Changes between 2.0.0 and 2.0.1 Changes between 2.0.0 and 2.0.1
---------------------------------------------- ----------------------------------------------

View File

@ -54,9 +54,18 @@ class MarkEvaluator:
%(self.name, self.expr, "\n".join(msg)), %(self.name, self.expr, "\n".join(msg)),
pytrace=False) pytrace=False)
def _getglobals(self):
d = {'os': py.std.os, 'sys': py.std.sys, 'config': self.item.config}
func = self.item.obj
try:
d.update(func.__globals__)
except AttributeError:
d.update(func.func_globals)
return d
def _istrue(self): def _istrue(self):
if self.holder: if self.holder:
d = {'os': py.std.os, 'sys': py.std.sys, 'config': self.item.config} d = self._getglobals()
if self.holder.args: if self.holder.args:
self.result = False self.result = False
for expr in self.holder.args: for expr in self.holder.args:
@ -64,7 +73,7 @@ class MarkEvaluator:
if isinstance(expr, str): if isinstance(expr, str):
result = cached_eval(self.item.config, expr, d) result = cached_eval(self.item.config, expr, d)
else: else:
result = expr pytest.fail("expression is not a string")
if result: if result:
self.result = True self.result = True
self.expr = expr self.expr = expr
@ -82,7 +91,7 @@ class MarkEvaluator:
if not hasattr(self, 'expr'): if not hasattr(self, 'expr'):
return "" return ""
else: else:
return "condition: " + self.expr return "condition: " + str(self.expr)
return expl return expl

View File

@ -17,5 +17,9 @@ def test_hello3():
def test_hello4(): def test_hello4():
assert 0 assert 0
@xfail('pytest.__version__[0] != "17"')
def test_hello5(): def test_hello5():
assert 0
def test_hello6():
pytest.xfail("reason") pytest.xfail("reason")

View File

@ -5,16 +5,17 @@ skip and xfail mechanisms
===================================================================== =====================================================================
You can skip or "xfail" test functions, either by marking functions You can skip or "xfail" test functions, either by marking functions
through a decorator or by calling the ``pytest.skip|xfail`` helpers. through a decorator or by calling the ``pytest.skip|xfail`` functions.
A *skip* means that you expect your test to pass unless a certain configuration or condition (e.g. wrong Python interpreter, missing dependency) prevents it to run. And *xfail* means that you expect your test to fail because there is an A *skip* means that you expect your test to pass unless a certain configuration or condition (e.g. wrong Python interpreter, missing dependency) prevents it to run. And *xfail* means that you expect your test to fail because there is an
implementation problem. py.test counts and lists *xfailing* tests separately implementation problem. py.test counts and lists *xfailing* tests separately
and you can provide info such as a bug number or a URL to provide a and you can provide info such as a bug number or a URL to provide a
human readable problem context. human readable problem context.
Usually detailed information about skipped/xfailed tests is not shown Usually detailed information about skipped/xfailed tests is not shown
to avoid cluttering the output. You can use the ``-r`` option to at the end of a test run to avoid cluttering the output. You can use
see details corresponding to the "short" letters shown in the the ``-r`` option to see details corresponding to the "short" letters
test progress:: shown in the test progress::
py.test -rxs # show extra info on skips and xfail tests py.test -rxs # show extra info on skips and xfail tests
@ -28,15 +29,32 @@ Skipping a single function
Here is an example for marking a test function to be skipped Here is an example for marking a test function to be skipped
when run on a Python3 interpreter:: when run on a Python3 interpreter::
import sys
@pytest.mark.skipif("sys.version_info >= (3,0)") @pytest.mark.skipif("sys.version_info >= (3,0)")
def test_function(): def test_function():
... ...
During test function setup the skipif condition is During test function setup the skipif condition is
evaluated by calling ``eval(expr, namespace)``. The namespace evaluated by calling ``eval(expr, namespace)``. The namespace
contains the ``sys`` and ``os`` modules and the test contains all the module globals of the test function so that
``config`` object. The latter allows you to skip based you can for example check for versions::
on a test configuration value e.g. like this::
import mymodule
@pytest.mark.skipif("mymodule.__version__ < '1.2'")
def test_function():
...
The test function will be skipped and not run if
mymodule is below the specified version. The reason
for specifying the condition as a string is mainly that
you can see more detailed reporting of xfail/skip reasons.
Actually, the namespace is first initialized by
putting the ``sys`` and ``os`` modules and the test
``config`` object into it. And is then updated with
the module globals. The latter allows you to skip based
on a test configuration value::
@pytest.mark.skipif("not config.getvalue('db')") @pytest.mark.skipif("not config.getvalue('db')")
def test_function(...): def test_function(...):
@ -52,7 +70,7 @@ at module level like this::
... ...
skip test functions of a class skip all test functions of a class
-------------------------------------- --------------------------------------
As with all function :ref:`marking` you can do it at As with all function :ref:`marking` you can do it at
@ -128,7 +146,7 @@ Running it with the report-on-xfail option gives this output::
========================= short test summary info ========================== ========================= short test summary info ==========================
XFAIL xfail_demo.py::test_hello XFAIL xfail_demo.py::test_hello
XFAIL xfail_demo.py::test_hello2 XFAIL xfail_demo.py::test_hello2
reason: [NOTRUN] reason: [NOTRUN]
XFAIL xfail_demo.py::test_hello3 XFAIL xfail_demo.py::test_hello3
condition: hasattr(os, 'sep') condition: hasattr(os, 'sep')
XFAIL xfail_demo.py::test_hello4 XFAIL xfail_demo.py::test_hello4

View File

@ -1,7 +1,7 @@
""" """
unit and functional testing with Python. unit and functional testing with Python.
""" """
__version__ = '2.0.2.dev1' __version__ = '2.0.2.dev2'
__all__ = ['main'] __all__ = ['main']
from _pytest.core import main, UsageError, _preloadplugins from _pytest.core import main, UsageError, _preloadplugins

View File

@ -22,7 +22,7 @@ def main():
name='pytest', name='pytest',
description='py.test: simple powerful testing with Python', description='py.test: simple powerful testing with Python',
long_description = long_description, long_description = long_description,
version='2.0.2.dev1', version='2.0.2.dev2',
url='http://pytest.org', url='http://pytest.org',
license='MIT license', license='MIT license',
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
@ -67,4 +67,4 @@ def make_entry_points():
return {'console_scripts': l} return {'console_scripts': l}
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -497,4 +497,34 @@ def test_errors_in_xfail_skip_expressions(testdir):
"*1 pass*2 error*", "*1 pass*2 error*",
]) ])
def test_xfail_skipif_with_globals(testdir):
testdir.makepyfile("""
import pytest
x = 3
@pytest.mark.skipif("x == 3")
def test_skip1():
pass
@pytest.mark.xfail("x == 3")
def test_boolean():
assert 0
""")
result = testdir.runpytest("-rsx")
result.stdout.fnmatch_lines([
"*SKIP*x == 3*",
"*XFAIL*test_boolean*",
"*x == 3*",
])
def test_direct_gives_error(testdir):
testdir.makepyfile("""
import pytest
@pytest.mark.skipif(True)
def test_skip1():
pass
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines([
"*1 error*",
])