From 4a436f225532e019787f5ef88be949e826928e21 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 27 Oct 2017 17:52:14 +0200 Subject: [PATCH 1/6] move responsibility for parameterset extraction into parameterset class --- _pytest/mark.py | 25 +++++++++++++++++++++++++ _pytest/python.py | 24 +++--------------------- changelog/2877.trivial | 1 + 3 files changed, 29 insertions(+), 21 deletions(-) create mode 100644 changelog/2877.trivial diff --git a/_pytest/mark.py b/_pytest/mark.py index 03b058d95..a5c972e5d 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -7,6 +7,7 @@ from collections import namedtuple from operator import attrgetter from six.moves import map from .deprecated import MARK_PARAMETERSET_UNPACKING +from .compat import NOTSET, getfslineno def alias(name, warning=None): @@ -67,6 +68,30 @@ class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')): return cls(argval, marks=newmarks, id=None) + @classmethod + def _for_parameterize(cls, argnames, argvalues, function): + if not isinstance(argnames, (tuple, list)): + argnames = [x.strip() for x in argnames.split(",") if x.strip()] + force_tuple = len(argnames) == 1 + else: + force_tuple = False + parameters = [ + ParameterSet.extract_from(x, legacy_force_tuple=force_tuple) + for x in argvalues] + del argvalues + + if not parameters: + fs, lineno = getfslineno(function) + reason = "got empty parameter set %r, function %s at %s:%d" % ( + argnames, function.__name__, fs, lineno) + mark = MARK_GEN.skip(reason=reason) + parameters.append(ParameterSet( + values=(NOTSET,) * len(argnames), + marks=[mark], + id=None, + )) + return argnames, parameters + class MarkerError(Exception): diff --git a/_pytest/python.py b/_pytest/python.py index c47422937..83e8dad9a 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -769,30 +769,12 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): to set a dynamic scope using test context or configuration. """ from _pytest.fixtures import scope2index - from _pytest.mark import MARK_GEN, ParameterSet + from _pytest.mark import ParameterSet from py.io import saferepr - - if not isinstance(argnames, (tuple, list)): - argnames = [x.strip() for x in argnames.split(",") if x.strip()] - force_tuple = len(argnames) == 1 - else: - force_tuple = False - parameters = [ - ParameterSet.extract_from(x, legacy_force_tuple=force_tuple) - for x in argvalues] + argnames, parameters = ParameterSet._for_parameterize( + argnames, argvalues, self.function) del argvalues - if not parameters: - fs, lineno = getfslineno(self.function) - reason = "got empty parameter set %r, function %s at %s:%d" % ( - argnames, self.function.__name__, fs, lineno) - mark = MARK_GEN.skip(reason=reason) - parameters.append(ParameterSet( - values=(NOTSET,) * len(argnames), - marks=[mark], - id=None, - )) - if scope is None: scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect) diff --git a/changelog/2877.trivial b/changelog/2877.trivial new file mode 100644 index 000000000..aaf58b039 --- /dev/null +++ b/changelog/2877.trivial @@ -0,0 +1 @@ +internal move of the parameterset extraction to a more maintainable place \ No newline at end of file From 821f9a94d8e5f8328abb43277781d29c03fa1b05 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 27 Oct 2017 18:34:52 +0200 Subject: [PATCH 2/6] deprecate the public internal PyCollector.makeitem method --- _pytest/deprecated.py | 5 +++++ _pytest/python.py | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/_pytest/deprecated.py b/_pytest/deprecated.py index 38e949677..e9231f221 100644 --- a/_pytest/deprecated.py +++ b/_pytest/deprecated.py @@ -40,3 +40,8 @@ MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning( " please use pytest.param(..., marks=...) instead.\n" "For more details, see: https://docs.pytest.org/en/latest/parametrize.html" ) + +COLLECTOR_MAKEITEM = RemovedInPytest4Warning( + "pycollector makeitem was removed " + "as it is an accidentially leaked internal api" +) \ No newline at end of file diff --git a/_pytest/python.py b/_pytest/python.py index c47422937..4a0501bf4 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -6,9 +6,11 @@ import inspect import sys import os import collections +import warnings from textwrap import dedent from itertools import count + import py import six from _pytest.mark import MarkerError @@ -18,6 +20,7 @@ import _pytest import pluggy from _pytest import fixtures from _pytest import main +from _pytest import deprecated from _pytest.compat import ( isclass, isfunction, is_generator, ascii_escaped, REGEX_TYPE, STRING_TYPES, NoneType, NOTSET, @@ -328,7 +331,7 @@ class PyCollector(PyobjMixin, main.Collector): if name in seen: continue seen[name] = True - res = self.makeitem(name, obj) + res = self._makeitem(name, obj) if res is None: continue if not isinstance(res, list): @@ -338,6 +341,10 @@ class PyCollector(PyobjMixin, main.Collector): return l def makeitem(self, name, obj): + warnings.warn(deprecated.COLLECTOR_MAKEITEM, stacklevel=2) + self._makeitem(name, obj) + + def _makeitem(self, name, obj): # assert self.ihook.fspath == self.fspath, self return self.ihook.pytest_pycollect_makeitem( collector=self, name=name, obj=obj) From 766de67392dda354fd37023f219dd71a4252efce Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 27 Oct 2017 18:42:46 -0200 Subject: [PATCH 3/6] Fix linting error in deprecated.py --- _pytest/deprecated.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/deprecated.py b/_pytest/deprecated.py index e9231f221..910510b01 100644 --- a/_pytest/deprecated.py +++ b/_pytest/deprecated.py @@ -44,4 +44,4 @@ MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning( COLLECTOR_MAKEITEM = RemovedInPytest4Warning( "pycollector makeitem was removed " "as it is an accidentially leaked internal api" -) \ No newline at end of file +) From d1aa553f739e91cd470eea23042b6c8bcebe9b6f Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 30 Oct 2017 16:44:49 +0100 Subject: [PATCH 4/6] add mocked integrationtest for the deprecationwarning of makeitem --- testing/python/test_deprecations.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 testing/python/test_deprecations.py diff --git a/testing/python/test_deprecations.py b/testing/python/test_deprecations.py new file mode 100644 index 000000000..5001f765f --- /dev/null +++ b/testing/python/test_deprecations.py @@ -0,0 +1,22 @@ +import pytest + +from _pytest.python import PyCollector + + +class PyCollectorMock(PyCollector): + """evil hack""" + + def __init__(self): + self.called = False + + def _makeitem(self, *k): + """hack to disable the actual behaviour""" + self.called = True + + +def test_pycollector_makeitem_is_deprecated(): + + collector = PyCollectorMock() + with pytest.deprecated_call(): + collector.makeitem('foo', 'bar') + assert collector.called From f3a119c06a8197ac986241e800beabfa63725f54 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 3 Nov 2017 16:37:18 -0200 Subject: [PATCH 5/6] Merge upstream/master into features --- AUTHORS | 1 + _pytest/assertion/rewrite.py | 16 ++++++--- _pytest/doctest.py | 2 +- _pytest/mark.py | 5 +-- changelog/1505.doc | 1 + changelog/2658.doc | 1 + changelog/2856.bugfix | 1 + changelog/2882.bugfix | 1 + doc/en/assert.rst | 4 +-- doc/en/example/parametrize.rst | 50 +++++++++++++++++++++++++++++ doc/en/example/pythoncollection.rst | 30 +++++++++-------- doc/en/fixture.rst | 44 +++++++++++++++++-------- doc/en/plugins.rst | 3 +- doc/en/skipping.rst | 12 ++++++- doc/en/writing_plugins.rst | 2 -- testing/test_assertrewrite.py | 48 +++++++++++++++++++-------- testing/test_doctest.py | 30 +++++++++++++++-- testing/test_mark.py | 17 ++++++++++ tox.ini | 6 ++-- 19 files changed, 215 insertions(+), 59 deletions(-) create mode 100644 changelog/1505.doc create mode 100644 changelog/2658.doc create mode 100644 changelog/2856.bugfix create mode 100644 changelog/2882.bugfix diff --git a/AUTHORS b/AUTHORS index ea3b7f50e..34c6f6437 100644 --- a/AUTHORS +++ b/AUTHORS @@ -47,6 +47,7 @@ Dave Hunt David Díaz-Barquero David Mohr David Vierra +Daw-Ran Liou Denis Kirisov Diego Russo Dmitry Dygalo diff --git a/_pytest/assertion/rewrite.py b/_pytest/assertion/rewrite.py index 6800f82e6..34fc0c8f6 100644 --- a/_pytest/assertion/rewrite.py +++ b/_pytest/assertion/rewrite.py @@ -591,23 +591,26 @@ class AssertionRewriter(ast.NodeVisitor): # docstrings and __future__ imports. aliases = [ast.alias(py.builtin.builtins.__name__, "@py_builtins"), ast.alias("_pytest.assertion.rewrite", "@pytest_ar")] - expect_docstring = True + doc = getattr(mod, "docstring", None) + expect_docstring = doc is None + if doc is not None and self.is_rewrite_disabled(doc): + return pos = 0 - lineno = 0 + lineno = 1 for item in mod.body: if (expect_docstring and isinstance(item, ast.Expr) and isinstance(item.value, ast.Str)): doc = item.value.s - if "PYTEST_DONT_REWRITE" in doc: - # The module has disabled assertion rewriting. + if self.is_rewrite_disabled(doc): return - lineno += len(doc) - 1 expect_docstring = False elif (not isinstance(item, ast.ImportFrom) or item.level > 0 or item.module != "__future__"): lineno = item.lineno break pos += 1 + else: + lineno = item.lineno imports = [ast.Import([alias], lineno=lineno, col_offset=0) for alias in aliases] mod.body[pos:pos] = imports @@ -633,6 +636,9 @@ class AssertionRewriter(ast.NodeVisitor): not isinstance(field, ast.expr)): nodes.append(field) + def is_rewrite_disabled(self, docstring): + return "PYTEST_DONT_REWRITE" in docstring + def variable(self): """Get a new variable.""" # Use a character invalid in python identifiers to avoid clashing. diff --git a/_pytest/doctest.py b/_pytest/doctest.py index 6016265a5..bba90e551 100644 --- a/_pytest/doctest.py +++ b/_pytest/doctest.py @@ -127,7 +127,7 @@ class DoctestItem(pytest.Item): lines = ["%03d %s" % (i + test.lineno + 1, x) for (i, x) in enumerate(lines)] # trim docstring error lines to 10 - lines = lines[example.lineno - 9:example.lineno + 1] + lines = lines[max(example.lineno - 9, 0):example.lineno + 1] else: lines = ['EXAMPLE LOCATION UNKNOWN, not showing all tests of that example'] indent = '>>>' diff --git a/_pytest/mark.py b/_pytest/mark.py index 03b058d95..8879b9fff 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -268,8 +268,9 @@ class MarkGenerator: pass self._markers = l = set() for line in self._config.getini("markers"): - beginning = line.split(":", 1) - x = beginning[0].split("(", 1)[0] + marker, _ = line.split(":", 1) + marker = marker.rstrip() + x = marker.split("(", 1)[0] l.add(x) if name not in self._markers: raise AttributeError("%r not a registered marker" % (name,)) diff --git a/changelog/1505.doc b/changelog/1505.doc new file mode 100644 index 000000000..1b303d1bd --- /dev/null +++ b/changelog/1505.doc @@ -0,0 +1 @@ +Introduce a dedicated section about conftest.py. diff --git a/changelog/2658.doc b/changelog/2658.doc new file mode 100644 index 000000000..2da7f3d6c --- /dev/null +++ b/changelog/2658.doc @@ -0,0 +1 @@ +Append example for pytest.param in the example/parametrize document. \ No newline at end of file diff --git a/changelog/2856.bugfix b/changelog/2856.bugfix new file mode 100644 index 000000000..7e5fc8fc7 --- /dev/null +++ b/changelog/2856.bugfix @@ -0,0 +1 @@ +Strip whitespace from marker names when reading them from INI config. diff --git a/changelog/2882.bugfix b/changelog/2882.bugfix new file mode 100644 index 000000000..2bda24c01 --- /dev/null +++ b/changelog/2882.bugfix @@ -0,0 +1 @@ +Show full context of doctest source in the pytest output, if the lineno of failed example in the docstring is < 9. \ No newline at end of file diff --git a/doc/en/assert.rst b/doc/en/assert.rst index a8ddaecd8..d9e044356 100644 --- a/doc/en/assert.rst +++ b/doc/en/assert.rst @@ -209,8 +209,8 @@ the ``pytest_assertrepr_compare`` hook. .. autofunction:: _pytest.hookspec.pytest_assertrepr_compare :noindex: -As an example consider adding the following hook in a conftest.py which -provides an alternative explanation for ``Foo`` objects:: +As an example consider adding the following hook in a :ref:`conftest.py ` +file which provides an alternative explanation for ``Foo`` objects:: # content of conftest.py from test_foocompare import Foo diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index ffeb5a951..1a8de235a 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -485,4 +485,54 @@ of our ``test_func1`` was skipped. A few notes: values as well. +Set marks or test ID for individual parametrized test +-------------------------------------------------------------------- +Use ``pytest.param`` to apply marks or set test ID to individual parametrized test. +For example:: + + # content of test_pytest_param_example.py + import pytest + @pytest.mark.parametrize('test_input,expected', [ + ('3+5', 8), + pytest.param('1+7', 8, + marks=pytest.mark.basic), + pytest.param('2+4', 6, + marks=pytest.mark.basic, + id='basic_2+4'), + pytest.param('6*9', 42, + marks=[pytest.mark.basic, pytest.mark.xfail], + id='basic_6*9'), + ]) + def test_eval(test_input, expected): + assert eval(test_input) == expected + +In this example, we have 4 parametrized tests. Except for the first test, +we mark the rest three parametrized tests with the custom marker ``basic``, +and for the fourth test we also use the built-in mark ``xfail`` to indicate this +test is expected to fail. For explicitness, we set test ids for some tests. + +Then run ``pytest`` with verbose mode and with only the ``basic`` marker:: + + pytest -v -m basic + ============================================ test session starts ============================================= + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + rootdir: $REGENDOC_TMPDIR, inifile: + collected 4 items + + test_pytest_param_example.py::test_eval[1+7-8] PASSED + test_pytest_param_example.py::test_eval[basic_2+4] PASSED + test_pytest_param_example.py::test_eval[basic_6*9] xfail + ========================================== short test summary info =========================================== + XFAIL test_pytest_param_example.py::test_eval[basic_6*9] + + ============================================= 1 tests deselected ============================================= + +As the result: + +- Four tests were collected +- One test was deselected because it doesn't have the ``basic`` mark. +- Three tests with the ``basic`` mark was selected. +- The test ``test_eval[1+7-8]`` passed, but the name is autogenerated and confusing. +- The test ``test_eval[basic_2+4]`` passed. +- The test ``test_eval[basic_6*9]`` was expected to fail and did fail. diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 8d36c2e37..5fb63035a 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -175,21 +175,23 @@ You can always peek at the collection tree without running tests like this:: ======= no tests ran in 0.12 seconds ======== -customizing test collection to find all .py files ---------------------------------------------------------- +.. _customizing-test-collection: + +Customizing test collection +--------------------------- .. regendoc:wipe -You can easily instruct ``pytest`` to discover tests from every python file:: - +You can easily instruct ``pytest`` to discover tests from every Python file:: # content of pytest.ini [pytest] python_files = *.py -However, many projects will have a ``setup.py`` which they don't want to be imported. Moreover, there may files only importable by a specific python version. -For such cases you can dynamically define files to be ignored by listing -them in a ``conftest.py`` file:: +However, many projects will have a ``setup.py`` which they don't want to be +imported. Moreover, there may files only importable by a specific python +version. For such cases you can dynamically define files to be ignored by +listing them in a ``conftest.py`` file:: # content of conftest.py import sys @@ -198,7 +200,7 @@ them in a ``conftest.py`` file:: if sys.version_info[0] > 2: collect_ignore.append("pkg/module_py2.py") -And then if you have a module file like this:: +and then if you have a module file like this:: # content of pkg/module_py2.py def test_only_on_python2(): @@ -207,13 +209,13 @@ And then if you have a module file like this:: except Exception, e: pass -and a setup.py dummy file like this:: +and a ``setup.py`` dummy file like this:: # content of setup.py 0/0 # will raise exception if imported -then a pytest run on Python2 will find the one test and will leave out the -setup.py file:: +If you run with a Python 2 interpreter then you will find the one test and will +leave out the ``setup.py`` file:: #$ pytest --collect-only ====== test session starts ====== @@ -225,13 +227,13 @@ setup.py file:: ====== no tests ran in 0.04 seconds ====== -If you run with a Python3 interpreter both the one test and the setup.py file -will be left out:: +If you run with a Python 3 interpreter both the one test and the ``setup.py`` +file will be left out:: $ pytest --collect-only ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 0 items - + ======= no tests ran in 0.12 seconds ======== diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index dace0514e..1d7ba8640 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -127,10 +127,39 @@ It's a prime example of `dependency injection`_ where fixture functions take the role of the *injector* and test functions are the *consumers* of fixture objects. +.. _`conftest.py`: +.. _`conftest`: + +``conftest.py``: sharing fixture functions +------------------------------------------ + +If during implementing your tests you realize that you +want to use a fixture function from multiple test files you can move it +to a ``conftest.py`` file. +You don't need to import the fixture you want to use in a test, it +automatically gets discovered by pytest. The discovery of +fixture functions starts at test classes, then test modules, then +``conftest.py`` files and finally builtin and third party plugins. + +You can also use the ``conftest.py`` file to implement +:ref:`local per-directory plugins `. + +Sharing test data +----------------- + +If you want to make test data from files available to your tests, a good way +to do this is by loading these data in a fixture for use by your tests. +This makes use of the automatic caching mechanisms of pytest. + +Another good approach is by adding the data files in the ``tests`` folder. +There are also community plugins available to help managing this aspect of +testing, e.g. `pytest-datadir `__ +and `pytest-datafiles `__. + .. _smtpshared: -Scope: Sharing a fixture across tests in a class, module or session -------------------------------------------------------------------- +Scope: sharing a fixture instance across tests in a class, module or session +---------------------------------------------------------------------------- .. regendoc:wipe @@ -878,17 +907,6 @@ All test methods in this TestClass will use the transaction fixture while other test classes or functions in the module will not use it unless they also add a ``transact`` reference. - -Shifting (visibility of) fixture functions ----------------------------------------------------- - -If during implementing your tests you realize that you -want to use a fixture function from multiple test files you can move it -to a :ref:`conftest.py ` file or even separately installable -:ref:`plugins ` without changing test code. The discovery of -fixtures functions starts at test classes, then test modules, then -``conftest.py`` files and finally builtin and third party plugins. - Overriding fixtures on various levels ------------------------------------- diff --git a/doc/en/plugins.rst b/doc/en/plugins.rst index bba7d3ecd..400418aee 100644 --- a/doc/en/plugins.rst +++ b/doc/en/plugins.rst @@ -91,7 +91,7 @@ environment you can type:: and will get an extended test header which shows activated plugins and their names. It will also print local plugins aka -:ref:`conftest.py ` files when they are loaded. +:ref:`conftest.py ` files when they are loaded. .. _`cmdunregister`: @@ -152,4 +152,3 @@ in the `pytest repository `_. _pytest.terminal _pytest.tmpdir _pytest.unittest - diff --git a/doc/en/skipping.rst b/doc/en/skipping.rst index dbe9c7f8d..3159d2083 100644 --- a/doc/en/skipping.rst +++ b/doc/en/skipping.rst @@ -3,7 +3,7 @@ .. _skipping: Skip and xfail: dealing with tests that cannot succeed -===================================================================== +====================================================== You can mark test functions that cannot be run on certain platforms or that you expect to fail so pytest can deal with them accordingly and @@ -152,6 +152,16 @@ will be skipped if any of the skip conditions is true. .. _`whole class- or module level`: mark.html#scoped-marking +Skipping files or directories +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes you may need to skip an entire file or directory, for example if the +tests rely on Python version-specific features or contain code that you do not +wish pytest to run. In this case, you must exclude the files and directories +from collection. Refer to :ref:`customizing-test-collection` for more +information. + + Skipping on a missing import dependency ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index 5f151b4bb..53a14cb0d 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -57,9 +57,7 @@ Plugin discovery order at tool startup .. _`pytest/plugin`: http://bitbucket.org/pytest-dev/pytest/src/tip/pytest/plugin/ .. _`conftest.py plugins`: -.. _`conftest.py`: .. _`localplugin`: -.. _`conftest`: .. _`local conftest plugins`: conftest.py: local per-directory plugins diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 07912f0b7..467f231a6 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -65,13 +65,18 @@ class TestAssertionRewrite(object): def test_place_initial_imports(self): s = """'Doc string'\nother = stuff""" m = rewrite(s) - assert isinstance(m.body[0], ast.Expr) - assert isinstance(m.body[0].value, ast.Str) - for imp in m.body[1:3]: + # Module docstrings in 3.7 are part of Module node, it's not in the body + # so we remove it so the following body items have the same indexes on + # all Python versions + if sys.version_info < (3, 7): + assert isinstance(m.body[0], ast.Expr) + assert isinstance(m.body[0].value, ast.Str) + del m.body[0] + for imp in m.body[0:2]: assert isinstance(imp, ast.Import) assert imp.lineno == 2 assert imp.col_offset == 0 - assert isinstance(m.body[3], ast.Assign) + assert isinstance(m.body[2], ast.Assign) s = """from __future__ import with_statement\nother_stuff""" m = rewrite(s) assert isinstance(m.body[0], ast.ImportFrom) @@ -80,16 +85,29 @@ class TestAssertionRewrite(object): assert imp.lineno == 2 assert imp.col_offset == 0 assert isinstance(m.body[3], ast.Expr) + s = """'doc string'\nfrom __future__ import with_statement""" + m = rewrite(s) + if sys.version_info < (3, 7): + assert isinstance(m.body[0], ast.Expr) + assert isinstance(m.body[0].value, ast.Str) + del m.body[0] + assert isinstance(m.body[0], ast.ImportFrom) + for imp in m.body[1:3]: + assert isinstance(imp, ast.Import) + assert imp.lineno == 2 + assert imp.col_offset == 0 s = """'doc string'\nfrom __future__ import with_statement\nother""" m = rewrite(s) - assert isinstance(m.body[0], ast.Expr) - assert isinstance(m.body[0].value, ast.Str) - assert isinstance(m.body[1], ast.ImportFrom) - for imp in m.body[2:4]: + if sys.version_info < (3, 7): + assert isinstance(m.body[0], ast.Expr) + assert isinstance(m.body[0].value, ast.Str) + del m.body[0] + assert isinstance(m.body[0], ast.ImportFrom) + for imp in m.body[1:3]: assert isinstance(imp, ast.Import) assert imp.lineno == 3 assert imp.col_offset == 0 - assert isinstance(m.body[4], ast.Expr) + assert isinstance(m.body[3], ast.Expr) s = """from . import relative\nother_stuff""" m = rewrite(s) for imp in m.body[0:2]: @@ -101,10 +119,14 @@ class TestAssertionRewrite(object): def test_dont_rewrite(self): s = """'PYTEST_DONT_REWRITE'\nassert 14""" m = rewrite(s) - assert len(m.body) == 2 - assert isinstance(m.body[0].value, ast.Str) - assert isinstance(m.body[1], ast.Assert) - assert m.body[1].msg is None + if sys.version_info < (3, 7): + assert len(m.body) == 2 + assert isinstance(m.body[0], ast.Expr) + assert isinstance(m.body[0].value, ast.Str) + del m.body[0] + else: + assert len(m.body) == 1 + assert m.body[0].msg is None def test_name(self): def f(): diff --git a/testing/test_doctest.py b/testing/test_doctest.py index b8fa1fb77..b15067f15 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -173,7 +173,7 @@ class TestDoctests(object): "*UNEXPECTED*ZeroDivision*", ]) - def test_docstring_context_around_error(self, testdir): + def test_docstring_partial_context_around_error(self, testdir): """Test that we show some context before the actual line of a failing doctest. """ @@ -199,7 +199,7 @@ class TestDoctests(object): ''') result = testdir.runpytest('--doctest-modules') result.stdout.fnmatch_lines([ - '*docstring_context_around_error*', + '*docstring_partial_context_around_error*', '005*text-line-3', '006*text-line-4', '013*text-line-11', @@ -213,6 +213,32 @@ class TestDoctests(object): assert 'text-line-2' not in result.stdout.str() assert 'text-line-after' not in result.stdout.str() + def test_docstring_full_context_around_error(self, testdir): + """Test that we show the whole context before the actual line of a failing + doctest, provided that the context is up to 10 lines long. + """ + testdir.makepyfile(''' + def foo(): + """ + text-line-1 + text-line-2 + + >>> 1 + 1 + 3 + """ + ''') + result = testdir.runpytest('--doctest-modules') + result.stdout.fnmatch_lines([ + '*docstring_full_context_around_error*', + '003*text-line-1', + '004*text-line-2', + '006*>>> 1 + 1', + 'Expected:', + ' 3', + 'Got:', + ' 2', + ]) + def test_doctest_linedata_missing(self, testdir): testdir.tmpdir.join('hello.py').write(_pytest._code.Source(""" class Fun(object): diff --git a/testing/test_mark.py b/testing/test_mark.py index dc51bbac0..9ae88a665 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -169,6 +169,23 @@ def test_markers_option(testdir): ]) +def test_ini_markers_whitespace(testdir): + testdir.makeini(""" + [pytest] + markers = + a1 : this is a whitespace marker + """) + testdir.makepyfile(""" + import pytest + + @pytest.mark.a1 + def test_markers(): + assert True + """) + rec = testdir.inline_run("--strict", "-m", "a1") + rec.assertoutcome(passed=1) + + def test_markers_option_with_plugin_in_current_dir(testdir): testdir.makeconftest('pytest_plugins = "flip_flop"') testdir.makepyfile(flip_flop="""\ diff --git a/tox.ini b/tox.ini index aaf39026b..09f59f5e3 100644 --- a/tox.ini +++ b/tox.ini @@ -54,8 +54,9 @@ deps = mock nose hypothesis>=3.5.2 +changedir=testing commands = - pytest -n1 -ra {posargs:testing} + pytest -n1 -ra {posargs:.} [testenv:py36-xdist] deps = {[testenv:py27-xdist]deps} @@ -81,10 +82,11 @@ deps = pytest-xdist>=1.13 hypothesis>=3.5.2 distribute = true +changedir=testing setenv = PYTHONDONTWRITEBYTECODE=1 commands = - pytest -n3 -ra {posargs:testing} + pytest -n3 -ra {posargs:.} [testenv:py27-trial] deps = twisted From 460cae02b0d48594730870ae0e6cf8cd9653b865 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 3 Nov 2017 16:51:59 -0200 Subject: [PATCH 6/6] Small formatting fix in CHANGELOG --- changelog/2877.trivial | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/2877.trivial b/changelog/2877.trivial index aaf58b039..c4198af97 100644 --- a/changelog/2877.trivial +++ b/changelog/2877.trivial @@ -1 +1 @@ -internal move of the parameterset extraction to a more maintainable place \ No newline at end of file +Internal move of the parameterset extraction to a more maintainable place.